diff --git a/src/bar.js b/src/bar.js index f376f4c..7d60ecc 100644 --- a/src/bar.js +++ b/src/bar.js @@ -159,9 +159,7 @@ export default class Bar { let $date_highlight = document.createElement('div'); $date_highlight.classList.add('date-highlight'); $date_highlight.classList.add(`highlight-${this.task.id}`); - // $date_highlight.style.height = this.height * 0.8 + 'px'; $date_highlight.style.width = this.width + 'px'; - // $date_highlight.style.top = this.gantt.config.header_height - 25 + 'px'; $date_highlight.style.left = x + 'px'; this.$date_highlight = $date_highlight; this.gantt.$lower_header.prepend($date_highlight); diff --git a/src/defaults.js b/src/defaults.js index 65d2c29..820894f 100644 --- a/src/defaults.js +++ b/src/defaults.js @@ -1,10 +1,16 @@ import date_utils from './date_utils'; +function getDecade(d) { + const year = d.getFullYear(); + return year - (year % 10) + ''; +} + const DEFAULT_VIEW_MODES = [ { name: 'Hour', padding: '7d', step: '1h', + format_string: 'YYYY-MM-DD HH', lower_text: 'HH', upper_text: (d, ld, lang) => !ld || d.getDate() !== ld.getDate() @@ -57,9 +63,10 @@ const DEFAULT_VIEW_MODES = [ name: 'Week', padding: '1m', step: '7d', + format_string: 'YYYY-MM-DD', column_width: 140, lower_text: (d, ld, lang) => - d.getMonth() !== ld.getMonth() + !ld || d.getMonth() !== ld.getMonth() ? date_utils.format(d, 'D MMM', lang) : date_utils.format(d, 'D', lang), upper_text: (d, ld, lang) => @@ -89,7 +96,9 @@ const DEFAULT_VIEW_MODES = [ step: '1y', column_width: 120, format_string: 'YYYY', - upper_text: 'YYYY', + upper_text: (d, ld, lang) => + !ld || getDecade(d) !== getDecade(ld) ? getDecade(d) : '', + lower_text: 'YYYY', default_snap: '30d', }, ]; @@ -102,9 +111,11 @@ const DEFAULT_OPTIONS = { container_height: 300, column_width: null, date_format: 'YYYY-MM-DD', + upper_header_height: 45, + lower_header_height: 30, snap_at: null, - infinite_padding: true, - holidays: { '#fff7ed': 'weekend' }, + infinite_padding: false, + holidays: { 'var(--g-weekend-highlight-color)': 'weekend' }, ignore: [], language: 'en', lines: 'both', diff --git a/src/index.js b/src/index.js index 7d8a83b..d59a8ac 100644 --- a/src/index.js +++ b/src/index.js @@ -78,6 +78,8 @@ export default class Gantt { const CSS_VARIABLES = { 'grid-height': 'container_height', 'bar-height': 'bar_height', + 'lower-header-height': 'lower_header_height', + 'upper-header-height': 'upper_header_height', }; for (let name in CSS_VARIABLES) { @@ -240,7 +242,10 @@ export default class Gantt { '--gv-column-width', this.config.column_width + 'px', ); - this.config.header_height = this.config.column_width * 2.5; + this.config.header_height = + this.options.lower_header_height + + this.options.upper_header_height + + 10; } setup_dates() { @@ -643,14 +648,47 @@ export default class Gantt { * * @returns Object containing the x-axis distance and date of the current date, or null if the current date is out of the gantt range. */ - highlightToday() { - const today = new Date(); - if (today < this.gantt_start || today > this.gantt_end) return null; + highlightNow() { + let now = new Date(); + if (now < this.gantt_start || now > this.gantt_end) return null; + + let current = new Date(), + el = this.$container.querySelector( + '.date_' + + sanitize( + date_utils.format( + current, + this.config.view_mode.format_string, + this.options.language, + ), + ), + ); + + // safety check to prevent infinite loop + let c = 0; + while (!el && c < this.config.step) { + current = date_utils.add(current, -1, this.config.unit); + el = this.$container.querySelector( + '.date_' + + sanitize( + date_utils.format( + current, + this.config.view_mode.format_string, + this.options.language, + ), + ), + ); + c++; + } + + el.classList.add('current-date-highlight'); + const diff_in_units = date_utils.diff( - today, + now, this.gantt_start, this.config.unit, ); + const left = (diff_in_units / this.config.step) * this.config.column_width; const height = @@ -658,11 +696,6 @@ export default class Gantt { this.tasks.length + 20 + this.options.padding / 2; - const date = date_utils.format( - today, - this.config.view_mode.format_string, - this.options.language, - ); this.$current_highlight = this.create_el({ top: this.config.header_height - 20, @@ -671,10 +704,6 @@ export default class Gantt { classes: 'current-highlight', append_to: this.$extras, }); - - let $today = this.$container - .querySelector('.date_' + date.replaceAll(' ', '_')) - .classList.add('current-date-highlight'); } make_grid_highlights() { @@ -722,7 +751,7 @@ export default class Gantt { }); } - const highlightDimensions = this.highlightToday(this.config.view_mode); + const highlightDimensions = this.highlightNow(this.config.view_mode); if (!highlightDimensions) return; } @@ -742,16 +771,18 @@ export default class Gantt { make_dates() { this.upper_texts_x = {}; this.get_dates_to_draw().forEach((date, i) => { - let $lower_text = this.create_el({ - left: date.lower_x, - top: date.lower_y, - classes: 'lower-text date_' + date.formatted_date, - append_to: this.$lower_header, - }); + if (date.lower_text) { + let $lower_text = this.create_el({ + left: date.lower_x, + top: date.lower_y, + classes: 'lower-text date_' + sanitize(date.formatted_date), + append_to: this.$lower_header, + }); - $lower_text.innerText = date.lower_text; - $lower_text.style.left = - +$lower_text.style.left.slice(0, -2) + 'px'; + $lower_text.innerText = date.lower_text; + $lower_text.style.left = + +$lower_text.style.left.slice(0, -2) + 'px'; + } if (date.upper_text) { this.upper_texts_x[date.upper_text] = date.upper_x; @@ -785,13 +816,9 @@ export default class Gantt { let column_width = this.config.column_width; - const base_pos = { - x: last_date_info - ? last_date_info.base_pos_x + last_date_info.column_width - : 0, - lower_y: this.config.column_width * 1.5, - upper_y: 15, - }; + const base_x = last_date_info + ? last_date_info.base_x + last_date_info.column_width + : 0; let upper_text = this.config.view_mode.upper_text; let lower_text = this.config.view_mode.lower_text; @@ -812,11 +839,11 @@ export default class Gantt { return { date, - formatted_date: date_utils - .format(date, this.config.view_mode.format_string) - .replaceAll(' ', '_'), + formatted_date: sanitize( + date_utils.format(date, this.config.view_mode.format_string), + ), column_width: this.config.column_width, - base_pos_x: base_pos.x, + base_x, upper_text: this.config.view_mode.upper_text( date, last_date, @@ -828,13 +855,13 @@ export default class Gantt { this.options.language, ), upper_x: - base_pos.x + + base_x + (column_width * this.config.view_mode.upper_text_frequency || 1) / 2, - upper_y: base_pos.upper_y, - lower_x: base_pos.x, - lower_y: base_pos.lower_y, + upper_y: 15, + lower_x: base_x, + lower_y: this.options.upper_header_height + 5, }; } @@ -924,6 +951,7 @@ export default class Gantt { this.$container.scrollLeft / this.config.column_width, this.config.unit, ); + let current_upper = this.config.view_mode.upper_text(this.current_date); let $el = this.upperTexts.find( (el) => el.textContent === current_upper, @@ -1094,9 +1122,11 @@ export default class Gantt { // Calculate current scroll position's upper text this.current_date = date_utils.add( this.gantt_start, - e.currentTarget.scrollLeft / this.config.column_width, + (e.currentTarget.scrollLeft / this.config.column_width) * + this.config.step, this.config.unit, ); + let current_upper = this.config.view_mode.upper_text( this.current_date, ); @@ -1107,8 +1137,9 @@ export default class Gantt { // Recalculate for smoother experience this.current_date = date_utils.add( this.gantt_start, - (e.currentTarget.scrollLeft + $el.clientWidth) / - this.config.column_width, + ((e.currentTarget.scrollLeft + $el.clientWidth) / + this.config.column_width) * + this.config.step, this.config.unit, ); current_upper = this.config.view_mode.upper_text(this.current_date); @@ -1436,3 +1467,7 @@ Gantt.VIEW_MODE = { function generate_id(task) { return task.name + '_' + Math.random().toString(36).slice(2, 12); } + +function sanitize(s) { + return s.replaceAll(' ', '_').replaceAll(':', '_').replaceAll('.', '_'); +} diff --git a/src/styles/gantt.css b/src/styles/gantt.css index 4f7ec11..e7b9f5e 100644 --- a/src/styles/gantt.css +++ b/src/styles/gantt.css @@ -53,18 +53,18 @@ } & .upper-header { - height: calc(var(--gv-column-width) * 1.5); + height: var(--gv-upper-header-height); } & .lower-header { - height: var(--gv-column-width); + height: var(--gv-lower-header-height); } & .lower-text { font-size: 12px; position: absolute; width: calc(var(--gv-column-width) * 0.8); - height: calc(var(--gv-column-width) * 0.8); + height: calc(var(--gv-lower-header-height) * 0.8); margin: 0 calc(var(--gv-column-width) * 0.1); align-content: center; text-align: center; @@ -77,13 +77,13 @@ font-weight: 500; font-size: 16px; color: var(--g-text-dark); + height: calc(var(--gv-lower-header-height) * 0.66); } & .current-upper { position: sticky; left: 0 !important; - padding: 0 10px; - height: 20px; + padding: 0 calc(var(--gv-lower-header-height) * 0.33); background: white; } @@ -128,9 +128,11 @@ & .date-highlight { background-color: var(--g-progress-color); - border-radius: 12px; - height: calc(var(--gv-bar-height) * 0.8); - top: calc(var(--gv-column-width) * 1.5); + border-radius: 20px; + height: var(--gv-lower-header-height); + top: calc( + var(--gv-upper-header-height) + var(--gv-lower-header-height) * 0.1 + ); position: absolute; display: none; } @@ -145,7 +147,7 @@ & .current-date-highlight { background: var(--g-blue-dark); color: var(--g-text-light); - border-radius: 100px; + border-radius: 5px; } & .holiday-label { diff --git a/src/styles/light.css b/src/styles/light.css index 2c38c79..b95c310 100644 --- a/src/styles/light.css +++ b/src/styles/light.css @@ -20,4 +20,5 @@ --g-blue-dark: #2c94ec; --g-header-background: #ffffff; --g-row-color: #fff; + --g-weekend-highlight-color: #bfdbfe; }