diff --git a/.gitignore b/.gitignore
index 53a8849e..9618ae05 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,7 +25,6 @@ speed-measure-plugin*.json
# IDE - VSCode
.vscode/*
-!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
diff --git a/docs/guides/basic/usage.md b/docs/guides/basic/usage.md
index 1d982d62..011ea94d 100644
--- a/docs/guides/basic/usage.md
+++ b/docs/guides/basic/usage.md
@@ -29,6 +29,31 @@ export class AppGanttExampleComponent {
}
```
+## 视图配置
+
+内置视图有一套默认的配置,如默认配置不满足需求时,可传入指定的 viewOptions 来进行自定义配置
+
+```
+
+ ...
+
+```
+
+```javascript
+ class GanttViewOptions {
+ start?: GanttDate; // 视图开始时间
+ end?: GanttDate; // 视图结束时间
+ min?: GanttDate; // 视图最小时间
+ max?: GanttDate; // 视图最大时间
+ cellWidth?: number; // 视图最小单元宽度(小时视图,最小单元就是每小时的宽度,日视图,最新单元就是每日显示的宽度)
+ addAmount?: number; // 横向滚动加载时,每次加载的量
+ addUnit?: GanttDateUtil; // 横向滚动加载时,每次加载的量的单位
+ dateFormat?: GanttDateFormat; // 设置视图日期格式,可用于多语言
+ datePrecisionUnit?: 'day' | 'hour' | 'minute'; // 日期精度单位,小时视图默认精度为分钟,其他视图默认精度为天
+ dragPreviewDateFormat?: string; // 拖拽预览日期格式设置
+ }
+```
+
## 如何设置分组
分组模式下我们还需要传入一个 `groups` 的数组,并且保证我们传入的 `items` 数据中设置了每个数据项的 `group_id`
diff --git a/docs/guides/intro/index.md b/docs/guides/intro/index.md
index c55472ea..d00f677d 100644
--- a/docs/guides/intro/index.md
+++ b/docs/guides/intro/index.md
@@ -10,7 +10,7 @@ order: 1
# 特性
-- 5 种视图(日、周、月、季、年)
+- 6 种内置视图(时、日、周、月、季、年),并支持自定义视图
- 任务分组展示
- 树形结构数据展示并支持异步加载
- 任务前后置依赖关联及展示
diff --git a/example/src/app/gantt-custom-view/custom-day-view.ts b/example/src/app/gantt-custom-view/custom-day-view.ts
index b9f3d1de..567bd7b4 100644
--- a/example/src/app/gantt-custom-view/custom-day-view.ts
+++ b/example/src/app/gantt-custom-view/custom-day-view.ts
@@ -30,11 +30,11 @@ export class GanttViewCustom extends GanttView {
super(start, end, Object.assign({}, viewOptions, options));
}
- startOf(date: GanttDate) {
+ viewStartOf(date: GanttDate) {
return date.startOfWeek({ weekStartsOn: 1 });
}
- endOf(date: GanttDate) {
+ viewEndOf(date: GanttDate) {
return date.endOfWeek({ weekStartsOn: 1 });
}
diff --git a/example/src/app/gantt/gantt.component.html b/example/src/app/gantt/gantt.component.html
index b55896f3..738b731d 100644
--- a/example/src/app/gantt/gantt.component.html
+++ b/example/src/app/gantt/gantt.component.html
@@ -53,22 +53,17 @@
(dragStarted)="onDragStarted($event)"
(dragEnded)="onDragEnded($event)"
>
-
-
- {{ item.id }}
-
-
{{ item.title }}
-
+
- {{ item.start * 1000 | date : 'yyyy-MM-dd' }}
+ {{ item.start * 1000 | date : 'yyyy-MM-dd HH:mm' }}
-
+
- {{ item.end * 1000 | date : 'yyyy-MM-dd' }}
+ {{ item.end * 1000 | date : 'yyyy-MM-dd HH:mm' }}
diff --git a/example/src/app/gantt/gantt.component.ts b/example/src/app/gantt/gantt.component.ts
index 24c4130a..7ce9c14d 100644
--- a/example/src/app/gantt/gantt.component.ts
+++ b/example/src/app/gantt/gantt.component.ts
@@ -1,26 +1,26 @@
-import { Component, OnInit, HostBinding, ViewChild, AfterViewInit } from '@angular/core';
+import { AfterViewInit, Component, HostBinding, OnInit, ViewChild } from '@angular/core';
import {
GanttBarClickEvent,
- GanttViewType,
+ GanttBaselineItem,
GanttDragEvent,
+ GanttItem,
GanttLineClickEvent,
GanttLinkDragEvent,
- GanttItem,
GanttPrintService,
- NgxGanttComponent,
GanttSelectedEvent,
- GanttBaselineItem,
- GanttView,
- GanttToolbarOptions,
- GanttTableDragEnterPredicateContext,
GanttTableDragDroppedEvent,
+ GanttTableDragEndedEvent,
+ GanttTableDragEnterPredicateContext,
GanttTableDragStartedEvent,
- GanttTableDragEndedEvent
+ GanttToolbarOptions,
+ GanttView,
+ GanttViewType,
+ NgxGanttComponent
} from 'ngx-gantt';
+import { ThyNotifyService } from 'ngx-tethys/notify';
import { finalize, of } from 'rxjs';
import { delay } from 'rxjs/operators';
-import { ThyNotifyService } from 'ngx-tethys/notify';
-import { randomItems, random } from '../helper';
+import { random, randomItems } from '../helper';
@Component({
selector: 'app-gantt-example',
@@ -30,6 +30,10 @@ import { randomItems, random } from '../helper';
})
export class AppGanttExampleComponent implements OnInit, AfterViewInit {
views = [
+ {
+ name: '小时',
+ value: GanttViewType.hour
+ },
{
name: '日',
value: GanttViewType.day
@@ -63,7 +67,7 @@ export class AppGanttExampleComponent implements OnInit, AfterViewInit {
loading = false;
items: GanttItem[] = [
- { id: '000000', title: 'Task 0', start: 1627729997, end: 1628421197 },
+ { id: '000000', title: 'Task 0', start: 1627729997, end: 1627769997 },
// { id: '000001', title: 'Task 1', start: 1617361997, end: 1625483597, links: ['000003', '000004', '000000'], },
{ id: '000001', title: 'Task 1', start: 1617361997, end: 1625483597, links: ['000003', '000004', '0000029'] },
{ id: '000002', title: 'Task 2', start: 1610536397, end: 1610622797, progress: 0.5 },
@@ -153,6 +157,8 @@ export class AppGanttExampleComponent implements OnInit, AfterViewInit {
}
selectedChange(event: GanttSelectedEvent) {
+ event.current && this.ganttComponent.scrollToDate(event.current?.start);
+
this.thyNotify.info(
'Event: selectedChange',
`当前选中的 item 的 id 为 ${(event.selectedValue as GanttItem[]).map((item) => item.id).join('、')}`
diff --git a/packages/gantt/src/class/event.ts b/packages/gantt/src/class/event.ts
index 0066dbd9..208582e5 100644
--- a/packages/gantt/src/class/event.ts
+++ b/packages/gantt/src/class/event.ts
@@ -35,6 +35,7 @@ export class GanttBarClickEvent {
export class GanttSelectedEvent {
event: Event;
+ current?: GanttItem;
selectedValue: GanttItem | GanttItem[];
}
diff --git a/packages/gantt/src/class/item.ts b/packages/gantt/src/class/item.ts
index 1875b81e..f86827fc 100644
--- a/packages/gantt/src/class/item.ts
+++ b/packages/gantt/src/class/item.ts
@@ -2,6 +2,9 @@ import { GanttDate } from '../utils/date';
import { BehaviorSubject } from 'rxjs';
import { GanttLink, GanttLinkType } from './link';
import { GanttViewType } from './view-type';
+import { GanttView } from '../views/view';
+
+const DEFAULT_FILL_INCREMENT_WIDTH = 120;
export interface GanttItemRefs {
width: number;
@@ -38,8 +41,8 @@ export interface GanttItem {
export class GanttItemInternal {
id: string;
title: string;
- start: GanttDate;
- end: GanttDate;
+ start: GanttDate | null;
+ end: GanttDate | null;
links: GanttLink[];
color?: string;
barStyle?: Partial;
@@ -53,17 +56,16 @@ export class GanttItemInternal {
children: GanttItemInternal[];
type?: GanttItemType;
progress?: number;
- fillDays?: number;
viewType?: GanttViewType;
- level?: number;
+ level: number;
get refs() {
return this.refs$.getValue();
}
- refs$ = new BehaviorSubject<{ width: number; x: number; y: number }>(null);
+ refs$ = new BehaviorSubject<{ width: number; x: number; y: number }>(null as any);
- constructor(item: GanttItem, level?: number, options?: { fillDays: number }) {
+ constructor(item: GanttItem, level: number, private view?: GanttView) {
this.origin = item;
this.id = this.origin.id;
this.links = (this.origin.links || []).map((link) => {
@@ -86,25 +88,21 @@ export class GanttItemInternal {
this.start = item.start ? new GanttDate(item.start) : null;
this.end = item.end ? new GanttDate(item.end) : null;
this.level = level;
- // 默认填充 30 天
- this.fillDays = options?.fillDays || 30;
this.children = (item.children || []).map((subItem) => {
- return new GanttItemInternal(subItem, level + 1, { fillDays: this.fillDays });
+ return new GanttItemInternal(subItem, level + 1, view);
});
this.type = this.origin.type || GanttItemType.bar;
this.progress = this.origin.progress;
- // fill days when start or end is null
- this.fillItemStartOrEnd(item);
+ this.fillDateWhenStartOrEndIsNil(item);
}
- fillItemStartOrEnd(item: GanttItem) {
- if (this.fillDays > 0) {
- const fillDays = this.fillDays - 1;
+ private fillDateWhenStartOrEndIsNil(item: GanttItem) {
+ if (this.view) {
if (item.start && !item.end) {
- this.end = new GanttDate(item.start).addDays(fillDays).endOfDay();
+ this.end = this.view.getDateByXPoint(this.view.getXPointByDate(new GanttDate(item.start)) + DEFAULT_FILL_INCREMENT_WIDTH);
}
if (!item.start && item.end) {
- this.start = new GanttDate(item.end).addDays(-fillDays).startOfDay();
+ this.start = this.view.getDateByXPoint(this.view.getXPointByDate(new GanttDate(item.end)) - DEFAULT_FILL_INCREMENT_WIDTH);
}
}
}
@@ -114,8 +112,8 @@ export class GanttItemInternal {
}
updateDate(start: GanttDate, end: GanttDate) {
- this.start = start.startOfDay();
- this.end = end.endOfDay();
+ this.start = start;
+ this.end = end;
this.origin.start = this.start.getUnixTime();
this.origin.end = this.end.getUnixTime();
}
@@ -127,7 +125,7 @@ export class GanttItemInternal {
addChildren(items: GanttItem[]) {
this.origin.children = items;
this.children = (items || []).map((subItem) => {
- return new GanttItemInternal(subItem, this.level + 1, { fillDays: this.fillDays });
+ return new GanttItemInternal(subItem, this.level + 1, this.view);
});
}
diff --git a/packages/gantt/src/class/test/item.spec.ts b/packages/gantt/src/class/test/item.spec.ts
index e1ba8e51..3ff890c0 100644
--- a/packages/gantt/src/class/test/item.spec.ts
+++ b/packages/gantt/src/class/test/item.spec.ts
@@ -2,6 +2,16 @@ import { GanttLinkType } from 'ngx-gantt';
import { GanttDate } from '../../utils/date';
import { GanttItem, GanttItemInternal } from '../item';
+class FakeView {
+ getDateByXPoint() {
+ return new GanttDate();
+ }
+
+ getXPointByDate() {
+ return 0;
+ }
+}
+
describe('GanttItemInternal', () => {
let ganttItemInternal: GanttItemInternal;
let ganttItem: GanttItem;
@@ -24,28 +34,40 @@ describe('GanttItemInternal', () => {
}
]
};
- ganttItemInternal = new GanttItemInternal(ganttItem);
+ ganttItemInternal = new GanttItemInternal(ganttItem, 0, new FakeView() as any);
});
it(`should has correct children`, () => {
expect(ganttItemInternal.children.length).toBe(1);
});
- it(`should has correct end`, () => {
- expect(ganttItemInternal.end.getUnixTime()).toBe(new GanttDate('2020-06-19 23:59:59').getUnixTime());
+ it(`should fill date when start or end date is nil`, () => {
+ const view = new FakeView();
+ const date = new GanttDate('2020-06-01 00:00:00');
+ spyOn(view, 'getDateByXPoint').and.returnValue(date);
- ganttItemInternal = new GanttItemInternal(ganttItem, 0, { fillDays: 1 });
- expect(ganttItemInternal.end.getUnixTime()).toBe(new GanttDate('2020-05-21 23:59:59').getUnixTime());
- });
+ let ganttItemInternal = new GanttItemInternal(
+ {
+ ...ganttItem,
+ start: null,
+ end: new GanttDate('2020-06-19 00:00:00').getUnixTime()
+ },
+ 0,
+ view as any
+ );
- it(`should has correct start`, () => {
- ganttItem.start = null;
- ganttItem.end = new GanttDate('2020-05-21 12:34:35').getUnixTime();
- ganttItemInternal = new GanttItemInternal(ganttItem);
- expect(ganttItemInternal.start.getUnixTime()).toBe(new GanttDate('2020-04-22 00:00:00').getUnixTime());
+ expect(ganttItemInternal.start.getUnixTime()).toBe(date.getUnixTime());
- ganttItemInternal = new GanttItemInternal(ganttItem, 0, { fillDays: 1 });
- expect(ganttItemInternal.start.getUnixTime()).toBe(new GanttDate('2020-05-21 00:00:00').getUnixTime());
+ ganttItemInternal = new GanttItemInternal(
+ {
+ ...ganttItem,
+ start: new GanttDate('2020-05-19 00:00:00').getUnixTime(),
+ end: null
+ },
+ 0,
+ view as any
+ );
+ expect(ganttItemInternal.end.getUnixTime()).toBe(date.getUnixTime());
});
it(`should update refs`, () => {
@@ -58,8 +80,8 @@ describe('GanttItemInternal', () => {
const start = new GanttDate('2020-04-21 12:34:35');
const end = new GanttDate('2020-09-21 12:34:35');
ganttItemInternal.updateDate(start, end);
- expect(ganttItemInternal.start.getUnixTime()).toBe(start.startOfDay().getUnixTime());
- expect(ganttItemInternal.end.getUnixTime()).toBe(end.endOfDay().getUnixTime());
+ expect(ganttItemInternal.start.getUnixTime()).toBe(start.getUnixTime());
+ expect(ganttItemInternal.end.getUnixTime()).toBe(end.getUnixTime());
});
it(`should add children`, () => {
diff --git a/packages/gantt/src/class/view-type.ts b/packages/gantt/src/class/view-type.ts
index 93ac2ca6..13f1651a 100644
--- a/packages/gantt/src/class/view-type.ts
+++ b/packages/gantt/src/class/view-type.ts
@@ -3,7 +3,8 @@ export enum GanttViewType {
quarter = 'quarter',
month = 'month',
year = 'year',
- week = 'week'
+ week = 'week',
+ hour = 'hour'
}
export const ganttViews = [
diff --git a/packages/gantt/src/components/bar/bar-drag.ts b/packages/gantt/src/components/bar/bar-drag.ts
index 503b075d..b9e25709 100644
--- a/packages/gantt/src/components/bar/bar-drag.ts
+++ b/packages/gantt/src/components/bar/bar-drag.ts
@@ -1,20 +1,21 @@
-import { Injectable, ElementRef, OnDestroy, NgZone } from '@angular/core';
-import { DragRef, DragDrop } from '@angular/cdk/drag-drop';
+import { DragDrop, DragRef } from '@angular/cdk/drag-drop';
+import { ElementRef, Injectable, NgZone, OnDestroy } from '@angular/core';
+import { Subject, animationFrameScheduler, fromEvent, interval } from 'rxjs';
+import { takeUntil } from 'rxjs/operators';
+import { GanttViewType } from '../../class';
+import { GanttItemInternal } from '../../class/item';
+import { GanttLinkType } from '../../class/link';
import { GanttDomService } from '../../gantt-dom.service';
import { GanttDragContainer, InBarPosition } from '../../gantt-drag-container';
-import { GanttItemInternal } from '../../class/item';
-import { GanttDate, differenceInCalendarDays, differenceInDays } from '../../utils/date';
-import { animationFrameScheduler, interval, Subject, fromEvent } from 'rxjs';
-import { takeUntil } from 'rxjs/operators';
import { GanttUpper } from '../../gantt-upper';
-import { GanttLinkType } from '../../class/link';
-import { passiveListenerOptions } from '../../utils/passive-listeners';
+import { GanttDate } from '../../utils/date';
import {
AutoScrollHorizontalDirection,
+ getAutoScrollSpeedRates,
getHorizontalScrollDirection,
- isPointerNearClientRect,
- getAutoScrollSpeedRates
+ isPointerNearClientRect
} from '../../utils/drag-scroll';
+import { passiveListenerOptions } from '../../utils/passive-listeners';
/**
* Proximity, as a ratio to width/height, at which a
@@ -88,7 +89,7 @@ export class GanttBarDrag implements OnDestroy {
private _horizontalScrollDirection = AutoScrollHorizontalDirection.NONE;
/** Record bar days when bar handle drag move. */
- private barHandleDragMoveRecordDays: number;
+ private barHandleDragMoveRecordDiffs: number;
/** Speed ratio for auto scroll */
private autoScrollSpeedRates = 1;
@@ -179,7 +180,7 @@ export class GanttBarDrag implements OnDestroy {
this.dragScrollDistance = 0;
this.barDragMoveDistance = 0;
this.item.updateRefs({
- width: this.ganttUpper.view.getDateRangeWidth(this.item.start.startOfDay(), this.item.end.endOfDay()),
+ width: this.ganttUpper.view.getDateRangeWidth(this.item.start, this.item.end),
x: this.ganttUpper.view.getXPointByDate(this.item.start),
y: (this.ganttUpper.styles.lineHeight - this.ganttUpper.styles.barHeight) / 2 - 1
});
@@ -223,7 +224,7 @@ export class GanttBarDrag implements OnDestroy {
});
dragRef.moved.subscribe((event) => {
- if (this.barHandleDragMoveRecordDays && this.barHandleDragMoveRecordDays > 0) {
+ if (this.barHandleDragMoveRecordDiffs && this.barHandleDragMoveRecordDiffs > 0) {
this.startScrollingIfNecessary(event.pointerPosition.x, event.pointerPosition.y);
}
this.barHandleDragMoveDistance = event.distance.x;
@@ -245,7 +246,7 @@ export class GanttBarDrag implements OnDestroy {
this.dragScrollDistance = 0;
this.barHandleDragMoveDistance = 0;
this.item.updateRefs({
- width: this.ganttUpper.view.getDateRangeWidth(this.item.start.startOfDay(), this.item.end.endOfDay()),
+ width: this.ganttUpper.view.getDateRangeWidth(this.item.start, this.item.end),
x: this.ganttUpper.view.getXPointByDate(this.item.start),
y: (this.ganttUpper.styles.lineHeight - this.ganttUpper.styles.barHeight) / 2 - 1
});
@@ -336,8 +337,8 @@ export class GanttBarDrag implements OnDestroy {
dragMaskElement.style.display = 'block';
dragBackdropElement.style.display = 'block';
// This will invalidate the layout, but we won't need re-layout, because we set styles previously.
- dragMaskElement.querySelector('.start').innerHTML = start.format('MM-dd');
- dragMaskElement.querySelector('.end').innerHTML = end.format('MM-dd');
+ dragMaskElement.querySelector('.start').innerHTML = start.format(this.ganttUpper.view.options.dragPreviewDateFormat);
+ dragMaskElement.querySelector('.end').innerHTML = end.format(this.ganttUpper.view.options.dragPreviewDateFormat);
}
private closeDragBackdrop() {
@@ -359,15 +360,19 @@ export class GanttBarDrag implements OnDestroy {
const currentX = this.item.refs.x + this.barDragMoveDistance + this.dragScrollDistance;
const currentDate = this.ganttUpper.view.getDateByXPoint(currentX);
const currentStartX = this.ganttUpper.view.getXPointByDate(currentDate);
- const dayWidth = this.ganttUpper.view.getDayOccupancyWidth(currentDate);
- const diffDays = differenceInCalendarDays(this.item.end.value, this.item.start.value);
- let start = currentDate;
- let end = currentDate.addDays(diffDays);
+ const diffs = this.ganttUpper.view.differenceByPrecisionUnit(this.item.end, this.item.start);
- if (currentX > currentStartX + dayWidth / 2) {
- start = start.addDays(1);
- end = end.addDays(1);
+ let start = currentDate;
+ let end = currentDate.add(diffs, this.ganttUpper.view?.options?.datePrecisionUnit);
+
+ // 日视图特殊逻辑处理
+ if (this.ganttUpper.view.viewType === GanttViewType.day) {
+ const dayWidth = this.ganttUpper.view.getDayOccupancyWidth(currentDate);
+ if (currentX > currentStartX + dayWidth / 2) {
+ start = start.addDays(1);
+ end = end.addDays(1);
+ }
}
if (this.dragScrolling) {
@@ -384,17 +389,16 @@ export class GanttBarDrag implements OnDestroy {
if (!this.isStartOrEndInsideView(start, end)) {
return;
}
-
- this.item.updateDate(start, end);
+ this.updateItemDate(start, end);
this.dragContainer.dragMoved.emit({ item: this.item.origin });
}
private barBeforeHandleDragMove() {
- const { x, start, oneDayWidth } = this.startOfBarHandle();
+ const { x, start, minRangeWidthWidth } = this.startOfBarHandle();
const width = this.item.refs.width + this.barHandleDragMoveAndScrollDistance * -1;
- const days = differenceInDays(this.item.end.value, start.value);
+ const diffs = this.ganttUpper.view.differenceByPrecisionUnit(this.item.end, start);
- if (width > dragMinWidth && days > 0) {
+ if (width > dragMinWidth && diffs > 0) {
this.barElement.style.width = width + 'px';
this.barElement.style.left = x + 'px';
this.openDragBackdrop(this.barElement, start, this.item.end);
@@ -403,41 +407,41 @@ export class GanttBarDrag implements OnDestroy {
return;
}
- this.item.updateDate(start, this.item.end);
+ this.updateItemDate(start, this.item.end);
} else {
- if (this.barHandleDragMoveRecordDays > 0 && days <= 0) {
- this.barElement.style.width = oneDayWidth + 'px';
+ if (this.barHandleDragMoveRecordDiffs > 0 && diffs <= 0) {
+ this.barElement.style.width = minRangeWidthWidth + 'px';
const x = this.ganttUpper.view.getXPointByDate(this.item.end);
this.barElement.style.left = x + 'px';
}
- this.openDragBackdrop(this.barElement, this.item.end.startOfDay(), this.item.end);
- this.item.updateDate(this.item.end.startOfDay(), this.item.end);
+ this.openDragBackdrop(this.barElement, this.item.end, this.item.end);
+ this.updateItemDate(this.item.end, this.item.end);
}
- this.barHandleDragMoveRecordDays = days;
+ this.barHandleDragMoveRecordDiffs = diffs;
this.dragContainer.dragMoved.emit({ item: this.item.origin });
}
private barAfterHandleDragMove() {
const { width, end } = this.endOfBarHandle();
- const days = differenceInDays(end.value, this.item.start.value);
+ const diffs = this.ganttUpper.view.differenceByPrecisionUnit(end, this.item.start);
- if (width > dragMinWidth && days > 0) {
+ if (width > dragMinWidth && diffs > 0) {
this.barElement.style.width = width + 'px';
this.openDragBackdrop(this.barElement, this.item.start, end);
if (!this.isStartOrEndInsideView(this.item.start, end)) {
return;
}
- this.item.updateDate(this.item.start, end);
+ this.updateItemDate(this.item.start, end);
} else {
- if (this.barHandleDragMoveRecordDays > 0 && days <= 0) {
- const oneDayWidth = this.ganttUpper.view.getDateRangeWidth(this.item.start, this.item.start.endOfDay());
- this.barElement.style.width = oneDayWidth + 'px';
+ if (this.barHandleDragMoveRecordDiffs > 0 && diffs <= 0) {
+ const minRangeWidth = this.ganttUpper.view.getMinRangeWidthByPrecisionUnit(this.item.start);
+ this.barElement.style.width = minRangeWidth + 'px';
}
- this.openDragBackdrop(this.barElement, this.item.start, this.item.start.endOfDay());
- this.item.updateDate(this.item.start, this.item.start.endOfDay());
+ this.openDragBackdrop(this.barElement, this.item.start, this.item.start);
+ this.updateItemDate(this.item.start, this.item.start);
}
- this.barHandleDragMoveRecordDays = days;
+ this.barHandleDragMoveRecordDiffs = diffs;
this.dragContainer.dragMoved.emit({ item: this.item.origin });
}
@@ -518,11 +522,11 @@ export class GanttBarDrag implements OnDestroy {
const xThreshold = clientWidth * DROP_PROXIMITY_THRESHOLD;
if (isBefore) {
- const { start, oneDayWidth } = this.startOfBarHandle();
+ const { start, minRangeWidthWidth } = this.startOfBarHandle();
const xPointerByEndDate = this.ganttUpper.view.getXPointByDate(this.item.end);
isStartGreaterThanEnd = start.value > this.item.end.value;
- isBarAppearsInView = xPointerByEndDate + oneDayWidth + xThreshold <= scrollLeft + clientWidth;
+ isBarAppearsInView = xPointerByEndDate + minRangeWidthWidth + xThreshold <= scrollLeft + clientWidth;
} else {
const { end } = this.endOfBarHandle();
const xPointerByStartDate = this.ganttUpper.view.getXPointByDate(this.item.start);
@@ -540,7 +544,7 @@ export class GanttBarDrag implements OnDestroy {
return {
x,
start: this.ganttUpper.view.getDateByXPoint(x),
- oneDayWidth: this.ganttUpper.view.getDateRangeWidth(this.item.end.startOfDay(), this.item.end)
+ minRangeWidthWidth: this.ganttUpper.view.getMinRangeWidthByPrecisionUnit(this.item.end)
};
}
@@ -570,6 +574,10 @@ export class GanttBarDrag implements OnDestroy {
}
}
+ private updateItemDate(start: GanttDate, end: GanttDate) {
+ this.item.updateDate(this.ganttUpper.view.startOfPrecision(start), this.ganttUpper.view.endOfPrecision(end));
+ }
+
createDrags(elementRef: ElementRef, item: GanttItemInternal, ganttUpper: GanttUpper) {
this.item = item;
this.barElement = elementRef.nativeElement;
diff --git a/packages/gantt/src/gantt-upper.ts b/packages/gantt/src/gantt-upper.ts
index bee8665a..65831a58 100644
--- a/packages/gantt/src/gantt-upper.ts
+++ b/packages/gantt/src/gantt-upper.ts
@@ -211,13 +211,13 @@ export abstract class GanttUpper implements OnChanges, OnInit, OnDestroy {
this.originItems.forEach((origin) => {
const group = this.groupsMap[origin.group_id];
if (group) {
- const item = new GanttItemInternal(origin, 0, { fillDays: this.view.options?.fillDays });
+ const item = new GanttItemInternal(origin, 0, this.view);
group.items.push(item);
}
});
} else {
this.originItems.forEach((origin) => {
- const item = new GanttItemInternal(origin, 0, { fillDays: this.view.options?.fillDays });
+ const item = new GanttItemInternal(origin, 0, this.view);
this.items.push(item);
});
}
@@ -377,7 +377,7 @@ export abstract class GanttUpper implements OnChanges, OnInit, OnDestroy {
computeItemsRefs(...items: GanttItemInternal[] | GanttBaselineItemInternal[]) {
items.forEach((item) => {
item.updateRefs({
- width: item.start && item.end ? this.view.getDateRangeWidth(item.start.startOfDay(), item.end.endOfDay()) : 0,
+ width: item.start && item.end ? this.view.getDateRangeWidth(item.start, item.end) : 0,
x: item.start ? this.view.getXPointByDate(item.start) : 0,
y: (this.styles.lineHeight - this.styles.barHeight) / 2 - 1
});
diff --git a/packages/gantt/src/gantt.component.ts b/packages/gantt/src/gantt.component.ts
index b5a36fd8..17ca033f 100644
--- a/packages/gantt/src/gantt.component.ts
+++ b/packages/gantt/src/gantt.component.ts
@@ -1,55 +1,55 @@
+import { CdkFixedSizeVirtualScroll, CdkVirtualForOf, CdkVirtualScrollViewport, ViewportRuler } from '@angular/cdk/scrolling';
+import { NgClass, NgIf, NgTemplateOutlet } from '@angular/common';
import {
+ AfterViewChecked,
+ AfterViewInit,
+ ChangeDetectionStrategy,
+ ChangeDetectorRef,
Component,
- OnInit,
+ ContentChild,
+ ContentChildren,
ElementRef,
- ChangeDetectionStrategy,
- Input,
EventEmitter,
- Output,
- ChangeDetectorRef,
+ Inject,
+ Input,
NgZone,
- ContentChildren,
+ OnChanges,
+ OnInit,
+ Output,
QueryList,
- AfterViewInit,
- ContentChild,
+ SimpleChanges,
TemplateRef,
- forwardRef,
- Inject,
ViewChild,
- OnChanges,
- SimpleChanges,
- AfterViewChecked
+ forwardRef
} from '@angular/core';
-import { takeUntil, take, finalize, skip } from 'rxjs/operators';
import { Observable, from } from 'rxjs';
-import { GanttUpper, GANTT_UPPER_TOKEN } from './gantt-upper';
+import { finalize, skip, take, takeUntil } from 'rxjs/operators';
import {
- GanttLinkDragEvent,
- GanttLineClickEvent,
- GanttItemInternal,
+ GanttGroupInternal,
GanttItem,
+ GanttItemInternal,
+ GanttLineClickEvent,
+ GanttLinkDragEvent,
GanttSelectedEvent,
- GanttGroupInternal,
- GanttVirtualScrolledIndexChangeEvent,
+ GanttTableDragEndedEvent,
GanttTableDragStartedEvent,
- GanttTableDragEndedEvent
+ GanttVirtualScrolledIndexChangeEvent
} from './class';
-import { NgxGanttTableColumnComponent } from './table/gantt-column.component';
-import { NgxGanttTableComponent } from './table/gantt-table.component';
-import { GANTT_ABSTRACT_TOKEN } from './gantt-abstract';
-import { GanttGlobalConfig, GANTT_GLOBAL_CONFIG } from './gantt.config';
-import { NgxGanttRootComponent } from './root.component';
-import { GanttDate } from './utils/date';
-import { CdkVirtualScrollViewport, ViewportRuler, CdkFixedSizeVirtualScroll, CdkVirtualForOf } from '@angular/cdk/scrolling';
-import { Dictionary, keyBy, recursiveItems, uniqBy } from './utils/helpers';
+import { GanttCalendarGridComponent } from './components/calendar/grid/calendar-grid.component';
+import { GanttCalendarHeaderComponent } from './components/calendar/header/calendar-header.component';
import { GanttDragBackdropComponent } from './components/drag-backdrop/drag-backdrop.component';
+import { GanttLoaderComponent } from './components/loader/loader.component';
import { GanttMainComponent } from './components/main/gantt-main.component';
-import { GanttCalendarGridComponent } from './components/calendar/grid/calendar-grid.component';
import { GanttTableBodyComponent } from './components/table/body/gantt-table-body.component';
-import { GanttLoaderComponent } from './components/loader/loader.component';
-import { NgIf, NgClass, NgTemplateOutlet } from '@angular/common';
-import { GanttCalendarHeaderComponent } from './components/calendar/header/calendar-header.component';
import { GanttTableHeaderComponent } from './components/table/header/gantt-table-header.component';
+import { GANTT_ABSTRACT_TOKEN } from './gantt-abstract';
+import { GANTT_UPPER_TOKEN, GanttUpper } from './gantt-upper';
+import { GANTT_GLOBAL_CONFIG, GanttGlobalConfig } from './gantt.config';
+import { NgxGanttRootComponent } from './root.component';
+import { NgxGanttTableColumnComponent } from './table/gantt-column.component';
+import { NgxGanttTableComponent } from './table/gantt-table.component';
+import { GanttDate } from './utils/date';
+import { Dictionary, keyBy, recursiveItems, uniqBy } from './utils/helpers';
@Component({
selector: 'ngx-gantt',
templateUrl: './gantt.component.html',
@@ -343,10 +343,10 @@ export class NgxGanttComponent extends GanttUpper implements OnInit, OnChanges,
const selectedIds = this.selectionModel.selected;
if (this.multiple) {
const _selectedValue = this.getGanttItems(selectedIds).map((item) => item.origin);
- this.selectedChange.emit({ event, selectedValue: _selectedValue });
+ this.selectedChange.emit({ event, current: selectedValue as GanttItem, selectedValue: _selectedValue });
} else {
const _selectedValue = this.getGanttItem(selectedIds[0])?.origin;
- this.selectedChange.emit({ event, selectedValue: _selectedValue });
+ this.selectedChange.emit({ event, current: selectedValue as GanttItem, selectedValue: _selectedValue });
}
}
diff --git a/packages/gantt/src/gantt.config.ts b/packages/gantt/src/gantt.config.ts
index 9bbb502b..3231365a 100644
--- a/packages/gantt/src/gantt.config.ts
+++ b/packages/gantt/src/gantt.config.ts
@@ -2,6 +2,8 @@ import { GanttLinkType, GanttLinkOptions, GanttLinkLineType } from './class/link
import { InjectionToken } from '@angular/core';
export interface GanttDateFormat {
+ hour?: string;
+ day?: string;
week?: string;
month?: string;
quarter?: string;
@@ -17,6 +19,8 @@ export interface GanttGlobalConfig {
export const defaultConfig = {
dateFormat: {
+ hour: 'HH:mm',
+ day: 'M月d日',
week: '第w周',
month: 'M月',
quarter: 'QQQ',
diff --git a/packages/gantt/src/utils/date.ts b/packages/gantt/src/utils/date.ts
index a7a5b6ff..f2fdbc24 100644
--- a/packages/gantt/src/utils/date.ts
+++ b/packages/gantt/src/utils/date.ts
@@ -26,11 +26,10 @@ import {
isWeekend,
getWeek,
isToday,
- differenceInDays,
- differenceInCalendarQuarters,
- eachMonthOfInterval,
- eachWeekOfInterval,
- eachDayOfInterval
+ startOfHour,
+ startOfMinute,
+ endOfHour,
+ endOfMinute
} from 'date-fns';
export {
@@ -63,9 +62,11 @@ export {
isToday,
differenceInDays,
differenceInCalendarQuarters,
+ differenceInMinutes,
eachMonthOfInterval,
eachWeekOfInterval,
- eachDayOfInterval
+ eachDayOfInterval,
+ eachHourOfInterval
} from 'date-fns';
export type GanttDateUtil = 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year';
@@ -209,6 +210,14 @@ export class GanttDate {
return new GanttDate(addYears(this.value, amount));
}
+ startOfMinute(): GanttDate {
+ return new GanttDate(startOfMinute(this.value));
+ }
+
+ startOfHour(): GanttDate {
+ return new GanttDate(startOfHour(this.value));
+ }
+
startOfDay(): GanttDate {
return new GanttDate(startOfDay(this.value));
}
@@ -229,6 +238,14 @@ export class GanttDate {
return new GanttDate(startOfYear(this.value));
}
+ endOfMinute(): GanttDate {
+ return new GanttDate(endOfMinute(this.value));
+ }
+
+ endOfHour(): GanttDate {
+ return new GanttDate(endOfHour(this.value));
+ }
+
endOfDay(): GanttDate {
return new GanttDate(endOfDay(this.value));
}
diff --git a/packages/gantt/src/views/day.ts b/packages/gantt/src/views/day.ts
index 6e50e9fa..feac91de 100644
--- a/packages/gantt/src/views/day.ts
+++ b/packages/gantt/src/views/day.ts
@@ -1,15 +1,14 @@
-import { GanttView, GanttViewOptions, primaryDatePointTop, secondaryDatePointTop, GanttViewDate } from './view';
-import { GanttDate, eachWeekOfInterval, eachDayOfInterval } from '../utils/date';
-import { GanttDatePoint } from '../class/date-point';
import { GanttViewType } from '../class';
+import { GanttDatePoint } from '../class/date-point';
+import { GanttDate, eachDayOfInterval, eachWeekOfInterval } from '../utils/date';
+import { GanttView, GanttViewDate, GanttViewOptions, primaryDatePointTop, secondaryDatePointTop } from './view';
const viewOptions: GanttViewOptions = {
cellWidth: 35,
start: new GanttDate().startOfYear().startOfWeek({ weekStartsOn: 1 }),
end: new GanttDate().endOfYear().endOfWeek({ weekStartsOn: 1 }),
addAmount: 1,
- addUnit: 'month',
- fillDays: 1
+ addUnit: 'month'
};
export class GanttViewDay extends GanttView {
@@ -23,11 +22,11 @@ export class GanttViewDay extends GanttView {
super(start, end, Object.assign({}, viewOptions, options));
}
- startOf(date: GanttDate) {
+ viewStartOf(date: GanttDate) {
return date.startOfWeek({ weekStartsOn: 1 });
}
- endOf(date: GanttDate) {
+ viewEndOf(date: GanttDate) {
return date.endOfWeek({ weekStartsOn: 1 });
}
diff --git a/packages/gantt/src/views/factory.ts b/packages/gantt/src/views/factory.ts
index c11f365c..7f48a35a 100644
--- a/packages/gantt/src/views/factory.ts
+++ b/packages/gantt/src/views/factory.ts
@@ -5,8 +5,10 @@ import { GanttViewQuarter } from './quarter';
import { GanttViewDay } from './day';
import { GanttViewWeek } from './week';
import { GanttViewYear } from './year';
+import { GanttViewHour } from './hour';
const ganttViewsMap = {
+ [GanttViewType.hour]: GanttViewHour,
[GanttViewType.day]: GanttViewDay,
[GanttViewType.week]: GanttViewWeek,
[GanttViewType.month]: GanttViewMonth,
diff --git a/packages/gantt/src/views/hour.ts b/packages/gantt/src/views/hour.ts
new file mode 100644
index 00000000..d05c1133
--- /dev/null
+++ b/packages/gantt/src/views/hour.ts
@@ -0,0 +1,102 @@
+import { GanttViewType } from '../class';
+import { GanttDatePoint } from '../class/date-point';
+import { GanttDate, differenceInMinutes, eachDayOfInterval, eachHourOfInterval } from '../utils/date';
+import { GanttView, GanttViewDate, GanttViewOptions, primaryDatePointTop, secondaryDatePointTop } from './view';
+
+const viewOptions: GanttViewOptions = {
+ cellWidth: 80,
+ start: new GanttDate().startOfMonth(),
+ end: new GanttDate().endOfMonth(),
+ datePrecisionUnit: 'minute',
+ addAmount: 1,
+ addUnit: 'week',
+ dragPreviewDateFormat: 'HH:mm'
+};
+
+export class GanttViewHour extends GanttView {
+ override showWeekBackdrop = true;
+
+ override showTimeline = true;
+
+ override viewType = GanttViewType.hour;
+
+ constructor(start: GanttViewDate, end: GanttViewDate, options?: GanttViewOptions) {
+ super(start, end, Object.assign({}, viewOptions, options));
+ }
+
+ viewStartOf(date: GanttDate) {
+ return date.startOfWeek({ weekStartsOn: 1 });
+ }
+
+ viewEndOf(date: GanttDate) {
+ return date.endOfWeek({ weekStartsOn: 1 });
+ }
+
+ getPrimaryWidth() {
+ return this.getCellWidth() * 24;
+ }
+
+ getDayOccupancyWidth(): number {
+ return this.cellWidth * 60;
+ }
+
+ private getHourOccupancyWidth() {
+ return this.getDayOccupancyWidth() / 60;
+ }
+
+ getPrimaryDatePoints(): GanttDatePoint[] {
+ const days = eachDayOfInterval({ start: this.start.value, end: this.end.value });
+ const points: GanttDatePoint[] = [];
+ for (let i = 0; i < days.length; i++) {
+ const start = this.start.addDays(i);
+ const point = new GanttDatePoint(
+ start,
+ start.format(this.options.dateFormat.day),
+ (this.getCellWidth() * 24) / 2 + i * (this.getCellWidth() * 24),
+ primaryDatePointTop
+ );
+ points.push(point);
+ }
+
+ return points;
+ }
+
+ getSecondaryDatePoints(): GanttDatePoint[] {
+ const hours = eachHourOfInterval({ start: this.start.value, end: this.end.value });
+ const points: GanttDatePoint[] = [];
+ for (let i = 0; i < hours.length; i++) {
+ const start = new GanttDate(hours[i]);
+ const point = new GanttDatePoint(
+ start,
+ start.format(this.options.dateFormat.hour),
+ i * this.getCellWidth() + this.getCellWidth() / 2,
+ secondaryDatePointTop,
+ {
+ isWeekend: start.isWeekend(),
+ isToday: start.isToday()
+ }
+ );
+ points.push(point);
+ }
+ return points;
+ }
+
+ override getDateIntervalWidth(start: GanttDate, end: GanttDate) {
+ let result = 0;
+ const minutes = differenceInMinutes(end.value, start.value);
+ for (let i = 0; i < minutes; i++) {
+ result += this.getHourOccupancyWidth() / 60;
+ }
+ result = minutes >= 0 ? result : -result;
+ return Number(result.toFixed(3));
+ }
+
+ override getDateByXPoint(x: number) {
+ const hourWidth = this.getHourOccupancyWidth();
+ const indexOfSecondaryDate = Math.max(Math.floor(x / hourWidth), 0);
+ const matchDate = this.secondaryDatePoints[Math.min(this.secondaryDatePoints.length - 1, indexOfSecondaryDate)];
+ const minuteWidth = hourWidth / 60;
+ const underOneHourMinutes = Math.floor((x % hourWidth) / minuteWidth);
+ return matchDate?.start.addMinutes(underOneHourMinutes);
+ }
+}
diff --git a/packages/gantt/src/views/month.ts b/packages/gantt/src/views/month.ts
index 849a49b1..f856f3b3 100644
--- a/packages/gantt/src/views/month.ts
+++ b/packages/gantt/src/views/month.ts
@@ -8,8 +8,7 @@ const viewOptions: GanttViewOptions = {
end: new GanttDate().endOfQuarter().addQuarters(2),
cellWidth: 280,
addAmount: 1,
- addUnit: 'quarter',
- fillDays: 30
+ addUnit: 'quarter'
};
export class GanttViewMonth extends GanttView {
@@ -19,11 +18,11 @@ export class GanttViewMonth extends GanttView {
super(start, end, Object.assign({}, viewOptions, options));
}
- startOf(date: GanttDate) {
+ viewStartOf(date: GanttDate) {
return date.startOfQuarter();
}
- endOf(date: GanttDate) {
+ viewEndOf(date: GanttDate) {
return date.endOfQuarter();
}
diff --git a/packages/gantt/src/views/quarter.ts b/packages/gantt/src/views/quarter.ts
index 596888c1..d6c00bd9 100644
--- a/packages/gantt/src/views/quarter.ts
+++ b/packages/gantt/src/views/quarter.ts
@@ -11,8 +11,7 @@ const viewOptions: GanttViewOptions = {
max: new GanttDate().addYears(2).endOfYear(),
cellWidth: 500,
addAmount: 1,
- addUnit: 'year',
- fillDays: 30
+ addUnit: 'year'
};
export class GanttViewQuarter extends GanttView {
@@ -22,11 +21,11 @@ export class GanttViewQuarter extends GanttView {
super(start, end, Object.assign({}, viewOptions, options));
}
- startOf(date: GanttDate) {
+ viewStartOf(date: GanttDate) {
return date.startOfYear();
}
- endOf(date: GanttDate) {
+ viewEndOf(date: GanttDate) {
return date.endOfYear();
}
diff --git a/packages/gantt/src/views/test/custom-view.mock.ts b/packages/gantt/src/views/test/custom-view.mock.ts
index 51a1116e..8b2ddd2e 100644
--- a/packages/gantt/src/views/test/custom-view.mock.ts
+++ b/packages/gantt/src/views/test/custom-view.mock.ts
@@ -29,11 +29,11 @@ export class GanttViewCustom extends GanttView {
super(start, end, Object.assign({}, viewOptions, options));
}
- startOf(date: GanttDate) {
+ viewStartOf(date: GanttDate) {
return date.startOfWeek({ weekStartsOn: 1 });
}
- endOf(date: GanttDate) {
+ viewEndOf(date: GanttDate) {
return date.endOfWeek({ weekStartsOn: 1 });
}
diff --git a/packages/gantt/src/views/test/day.spec.ts b/packages/gantt/src/views/test/day.spec.ts
index fd40fcd5..8d3430a0 100644
--- a/packages/gantt/src/views/test/day.spec.ts
+++ b/packages/gantt/src/views/test/day.spec.ts
@@ -14,12 +14,12 @@ describe('GanttViewDay', () => {
});
it(`should has correct view start`, () => {
- const startOfDay = ganttViewDay.startOf(date.start.date).getUnixTime();
+ const startOfDay = ganttViewDay.viewStartOf(date.start.date).getUnixTime();
expect(startOfDay).toEqual(new GanttDate('2019-12-30 00:00:00').getUnixTime());
});
it(`should has correct view end`, () => {
- const endOfDay = ganttViewDay.endOf(date.end.date).getUnixTime();
+ const endOfDay = ganttViewDay.viewEndOf(date.end.date).getUnixTime();
expect(endOfDay).toEqual(new GanttDate('2021-01-03 23:59:59').getUnixTime());
});
diff --git a/packages/gantt/src/views/test/hour.spec.ts b/packages/gantt/src/views/test/hour.spec.ts
new file mode 100644
index 00000000..4177955a
--- /dev/null
+++ b/packages/gantt/src/views/test/hour.spec.ts
@@ -0,0 +1,46 @@
+import { eachDayOfInterval, eachHourOfInterval } from '../../utils/date';
+import { GanttViewHour } from '../hour';
+import { date } from './mock';
+
+describe('GanttViewHour', () => {
+ let ganttViewHour: GanttViewHour;
+
+ const hourWidth = 30;
+
+ beforeEach(() => {
+ ganttViewHour = new GanttViewHour(date.start, date.end, {
+ cellWidth: hourWidth
+ });
+ });
+
+ it(`should correct getPrimaryDatePoints`, () => {
+ const points = ganttViewHour.getPrimaryDatePoints();
+ const days = eachDayOfInterval({
+ start: ganttViewHour.viewStartOf(date.start.date).value,
+ end: ganttViewHour.viewEndOf(date.end.date).value
+ });
+ expect(points.length).toEqual(days.length);
+ });
+
+ it(`should correct getSecondaryDatePoints`, () => {
+ const points = ganttViewHour.getSecondaryDatePoints();
+ const hours = eachHourOfInterval({
+ start: ganttViewHour.viewStartOf(date.start.date).value,
+ end: ganttViewHour.viewEndOf(date.end.date).value
+ });
+ expect(points.length).toEqual(hours.length);
+ });
+
+ it(`should correct getDateIntervalWidth`, () => {
+ let width = ganttViewHour.getDateIntervalWidth(ganttViewHour.start, ganttViewHour.start.addDays(5));
+ expect(width).toEqual(5 * (hourWidth * 24));
+
+ width = ganttViewHour.getDateIntervalWidth(ganttViewHour.start, ganttViewHour.start.addDays(5).addMinutes(20));
+ expect(width).toEqual(5 * (hourWidth * 24) + hourWidth * (20 / 60));
+ });
+
+ it(`should correct getDateByXPoint`, () => {
+ let date = ganttViewHour.getDateByXPoint(hourWidth * 40);
+ expect(date.getUnixTime()).toEqual(ganttViewHour.start.addHours(40).getUnixTime());
+ });
+});
diff --git a/packages/gantt/src/views/test/month.spec.ts b/packages/gantt/src/views/test/month.spec.ts
index 65e30122..ca5181bf 100644
--- a/packages/gantt/src/views/test/month.spec.ts
+++ b/packages/gantt/src/views/test/month.spec.ts
@@ -14,12 +14,12 @@ describe('GanttViewMonth', () => {
});
it(`should has correct view start`, () => {
- const startOfMonth = ganttViewMonth.startOf(date.start.date).getUnixTime();
+ const startOfMonth = ganttViewMonth.viewStartOf(date.start.date).getUnixTime();
expect(startOfMonth).toEqual(new GanttDate('2020-01-01 00:00:00').getUnixTime());
});
it(`should has correct view end`, () => {
- const endOfMonth = ganttViewMonth.endOf(date.end.date).getUnixTime();
+ const endOfMonth = ganttViewMonth.viewEndOf(date.end.date).getUnixTime();
expect(endOfMonth).toEqual(new GanttDate('2020-12-31 23:59:59').getUnixTime());
});
diff --git a/packages/gantt/src/views/test/quarter.spec.ts b/packages/gantt/src/views/test/quarter.spec.ts
index efadcc55..b969136f 100644
--- a/packages/gantt/src/views/test/quarter.spec.ts
+++ b/packages/gantt/src/views/test/quarter.spec.ts
@@ -14,12 +14,12 @@ describe('GanttViewQuarter', () => {
});
it(`should has correct view start`, () => {
- const startOfQuarter = ganttViewQuarter.startOf(date.start.date).getUnixTime();
+ const startOfQuarter = ganttViewQuarter.viewStartOf(date.start.date).getUnixTime();
expect(startOfQuarter).toEqual(new GanttDate('2020-01-01 00:00:00').getUnixTime());
});
it(`should has correct view end`, () => {
- const endOfQuarter = ganttViewQuarter.endOf(date.end.date).getUnixTime();
+ const endOfQuarter = ganttViewQuarter.viewEndOf(date.end.date).getUnixTime();
expect(endOfQuarter).toEqual(new GanttDate('2020-12-31 23:59:59').getUnixTime());
});
diff --git a/packages/gantt/src/views/test/view.spec.ts b/packages/gantt/src/views/test/view.spec.ts
index 11e93a00..0073062c 100644
--- a/packages/gantt/src/views/test/view.spec.ts
+++ b/packages/gantt/src/views/test/view.spec.ts
@@ -1,6 +1,6 @@
+import { differenceInHours, differenceInMinutes } from 'date-fns';
import { GanttDatePoint } from '../../class';
import { GanttDate } from '../../utils/date';
-
import { GanttView, GanttViewDate, GanttViewOptions } from '../view';
import { date, today } from './mock';
@@ -9,11 +9,11 @@ class GanttViewMock extends GanttView {
super(start, end, options);
}
- startOf(): GanttDate {
+ viewStartOf(): GanttDate {
return new GanttDate('2020-01-01 00:00:00');
}
- endOf(): GanttDate {
+ viewEndOf(): GanttDate {
return new GanttDate('2020-12-31 23:59:59');
}
@@ -139,6 +139,27 @@ describe('GanttView', () => {
it(`should get date range width`, () => {
const width = ganttView.getDateRangeWidth(new GanttDate('2020-03-01 00:00:00'), new GanttDate('2020-05-01 00:00:00'));
- expect(width).toEqual(610);
+ expect(width).toEqual(620);
+ });
+
+ it(`should get diff precision date`, () => {
+ let view = new GanttViewMock(date.start, date.end, { datePrecisionUnit: 'hour' });
+ const dateWithTime = new GanttDate('2022-10-10 12:50:34');
+ expect(view.startOfPrecision(dateWithTime).getUnixTime()).toEqual(dateWithTime.startOfHour().getUnixTime());
+ expect(view.endOfPrecision(dateWithTime).getUnixTime()).toEqual(dateWithTime.endOfHour().getUnixTime());
+
+ view = new GanttViewMock(date.start, date.end, { datePrecisionUnit: 'minute' });
+ expect(view.startOfPrecision(dateWithTime).getUnixTime()).toEqual(dateWithTime.startOfMinute().getUnixTime());
+ expect(view.endOfPrecision(dateWithTime).getUnixTime()).toEqual(dateWithTime.endOfMinute().getUnixTime());
+ });
+
+ it(`should date difference value by precision unit`, () => {
+ let view = new GanttViewMock(date.start, date.end, { datePrecisionUnit: 'hour' });
+ const startDate = new GanttDate('2022-10-10 12:50:34');
+ const endDate = new GanttDate('2022-12-10 10:50:34');
+ expect(view.differenceByPrecisionUnit(endDate, startDate)).toEqual(differenceInHours(endDate.value, startDate.value));
+
+ view = new GanttViewMock(date.start, date.end, { datePrecisionUnit: 'minute' });
+ expect(view.differenceByPrecisionUnit(endDate, startDate)).toEqual(differenceInMinutes(endDate.value, startDate.value));
});
});
diff --git a/packages/gantt/src/views/test/week.spec.ts b/packages/gantt/src/views/test/week.spec.ts
index 79f99f58..152e67e1 100644
--- a/packages/gantt/src/views/test/week.spec.ts
+++ b/packages/gantt/src/views/test/week.spec.ts
@@ -14,12 +14,12 @@ describe('GanttViewWeek', () => {
});
it(`should has correct view start`, () => {
- const startOfWeek = ganttViewWeek.startOf(date.start.date).getUnixTime();
+ const startOfWeek = ganttViewWeek.viewStartOf(date.start.date).getUnixTime();
expect(startOfWeek).toEqual(new GanttDate('2019-12-30 00:00:00').getUnixTime());
});
it(`should has correct view end`, () => {
- const endOfWeek = ganttViewWeek.endOf(date.end.date).getUnixTime();
+ const endOfWeek = ganttViewWeek.viewEndOf(date.end.date).getUnixTime();
expect(endOfWeek).toEqual(new GanttDate('2021-01-03 23:59:59').getUnixTime());
});
diff --git a/packages/gantt/src/views/test/year.spec.ts b/packages/gantt/src/views/test/year.spec.ts
index 46a09691..69a8d362 100644
--- a/packages/gantt/src/views/test/year.spec.ts
+++ b/packages/gantt/src/views/test/year.spec.ts
@@ -14,12 +14,12 @@ describe('GanttViewYear', () => {
});
it(`should has correct view start`, () => {
- const startOfDay = ganttViewYear.startOf(date.start.date).getUnixTime();
+ const startOfDay = ganttViewYear.viewStartOf(date.start.date).getUnixTime();
expect(startOfDay).toEqual(new GanttDate('2020-01-01 00:00:00').getUnixTime());
});
it(`should has correct view end`, () => {
- const endOfDay = ganttViewYear.endOf(date.end.date).getUnixTime();
+ const endOfDay = ganttViewYear.viewEndOf(date.end.date).getUnixTime();
expect(endOfDay).toEqual(new GanttDate('2020-12-31 23:59:59').getUnixTime());
});
diff --git a/packages/gantt/src/views/view.ts b/packages/gantt/src/views/view.ts
index aade6405..ef705095 100644
--- a/packages/gantt/src/views/view.ts
+++ b/packages/gantt/src/views/view.ts
@@ -1,8 +1,9 @@
-import { GanttDate, differenceInDays, GanttDateUtil } from '../utils/date';
-import { GanttDatePoint } from '../class/date-point';
+import { differenceInCalendarDays, differenceInHours, differenceInMinutes } from 'date-fns';
import { BehaviorSubject } from 'rxjs';
-import { defaultConfig, GanttDateFormat } from '../gantt.config';
import { GanttViewType } from '../class';
+import { GanttDatePoint } from '../class/date-point';
+import { GanttDateFormat, defaultConfig } from '../gantt.config';
+import { GanttDate, GanttDateUtil, differenceInDays } from '../utils/date';
export const primaryDatePointTop = 18;
@@ -22,8 +23,8 @@ export interface GanttViewOptions {
addAmount?: number;
addUnit?: GanttDateUtil;
dateFormat?: GanttDateFormat;
- // fill days when start or end is null
- fillDays?: number;
+ datePrecisionUnit?: 'day' | 'hour' | 'minute';
+ dragPreviewDateFormat?: string;
// custom key and value
[key: string]: any;
}
@@ -31,7 +32,9 @@ export interface GanttViewOptions {
const viewOptions: GanttViewOptions = {
min: new GanttDate().addYears(-1).startOfYear(),
max: new GanttDate().addYears(1).endOfYear(),
- dateFormat: defaultConfig.dateFormat
+ dateFormat: defaultConfig.dateFormat,
+ datePrecisionUnit: 'day',
+ dragPreviewDateFormat: 'MM-dd'
};
export abstract class GanttView {
@@ -68,19 +71,35 @@ export abstract class GanttView {
constructor(start: GanttViewDate, end: GanttViewDate, options: GanttViewOptions) {
this.options = Object.assign({}, viewOptions, options);
const startDate = start.isCustom
- ? this.startOf(start.date)
- : this.startOf(start.date.value < this.options.start.value ? start.date : this.options.start);
+ ? this.viewStartOf(start.date)
+ : this.viewStartOf(start.date.value < this.options.start.value ? start.date : this.options.start);
const endDate = end.isCustom
- ? this.endOf(end.date)
- : this.endOf(end.date.value > this.options.end.value ? end.date : this.options.end);
+ ? this.viewEndOf(end.date)
+ : this.viewEndOf(end.date.value > this.options.end.value ? end.date : this.options.end);
this.start$ = new BehaviorSubject(startDate);
this.end$ = new BehaviorSubject(endDate);
this.initialize();
}
- abstract startOf(date: GanttDate): GanttDate;
+ abstract viewStartOf(date: GanttDate): GanttDate;
- abstract endOf(date: GanttDate): GanttDate;
+ abstract viewEndOf(date: GanttDate): GanttDate;
+
+ /**
+ * deprecated, please use viewStartOf()
+ * @deprecated
+ */
+ startOf(date: GanttDate): GanttDate {
+ return this.viewStartOf(date);
+ }
+
+ /**
+ * deprecated, please use viewEndOf()
+ * @deprecated
+ */
+ endOf(date: GanttDate): GanttDate {
+ return this.viewEndOf(date);
+ }
// 获取一级时间网格合并后的宽度
abstract getPrimaryWidth(): number;
@@ -94,7 +113,40 @@ export abstract class GanttView {
// 获取二级时间点(坐标,显示名称)
abstract getSecondaryDatePoints(): GanttDatePoint[];
- protected getDateIntervalWidth(start: GanttDate, end: GanttDate) {
+ startOfPrecision(date: GanttDate) {
+ switch (this.options.datePrecisionUnit) {
+ case 'minute':
+ return date.startOfMinute();
+ case 'hour':
+ return date.startOfHour();
+ default:
+ return date.startOfDay();
+ }
+ }
+
+ endOfPrecision(date: GanttDate) {
+ switch (this.options.datePrecisionUnit) {
+ case 'minute':
+ return date.endOfMinute();
+ case 'hour':
+ return date.endOfHour();
+ default:
+ return date.endOfDay();
+ }
+ }
+
+ differenceByPrecisionUnit(dateLeft: GanttDate, dateRight: GanttDate) {
+ switch (this.options.datePrecisionUnit) {
+ case 'minute':
+ return differenceInMinutes(dateLeft.value, dateRight.value);
+ case 'hour':
+ return differenceInHours(dateLeft.value, dateRight.value);
+ default:
+ return differenceInCalendarDays(dateLeft.value, dateRight.value);
+ }
+ }
+
+ getDateIntervalWidth(start: GanttDate, end: GanttDate) {
let result = 0;
const days = differenceInDays(end.value, start.value);
for (let i = 0; i < Math.abs(days); i++) {
@@ -113,7 +165,7 @@ export abstract class GanttView {
}
addStartDate() {
- const start = this.startOf(this.start.add(this.options.addAmount * -1, this.options.addUnit));
+ const start = this.viewStartOf(this.start.add(this.options.addAmount * -1, this.options.addUnit));
if (start.value >= this.options.min.value) {
const origin = this.start;
this.start$.next(start);
@@ -124,7 +176,7 @@ export abstract class GanttView {
}
addEndDate() {
- const end = this.endOf(this.end.add(this.options.addAmount, this.options.addUnit));
+ const end = this.viewEndOf(this.end.add(this.options.addAmount, this.options.addUnit));
if (end.value <= this.options.max.value) {
const origin = this.end;
this.end$.next(end);
@@ -135,8 +187,8 @@ export abstract class GanttView {
}
updateDate(start: GanttDate, end: GanttDate) {
- start = this.startOf(start);
- end = this.endOf(end);
+ start = this.viewStartOf(start);
+ end = this.viewEndOf(end);
if (start.value < this.start.value) {
this.start$.next(start);
}
@@ -191,6 +243,18 @@ export abstract class GanttView {
// 获取指定时间范围的宽度
getDateRangeWidth(start: GanttDate, end: GanttDate) {
// addSeconds(1) 是因为计算相差天会以一个整天来计算 end时间一般是59分59秒不是一个整天,所以需要加1
- return this.getDateIntervalWidth(start, end.addSeconds(1));
+ return this.getDateIntervalWidth(this.startOfPrecision(start), this.endOfPrecision(end).addSeconds(1));
+ }
+
+ // 根据日期精度获取最小时间范围的宽度
+ getMinRangeWidthByPrecisionUnit(date: GanttDate) {
+ switch (this.options.datePrecisionUnit) {
+ case 'minute':
+ return this.getDayOccupancyWidth(date) / 24 / 60;
+ case 'hour':
+ return this.getDayOccupancyWidth(date) / 24;
+ default:
+ return this.getDayOccupancyWidth(date);
+ }
}
}
diff --git a/packages/gantt/src/views/week.ts b/packages/gantt/src/views/week.ts
index 35ebaa31..d7e6e98b 100644
--- a/packages/gantt/src/views/week.ts
+++ b/packages/gantt/src/views/week.ts
@@ -8,8 +8,7 @@ const viewOptions: GanttViewOptions = {
start: new GanttDate().startOfYear().startOfWeek({ weekStartsOn: 1 }),
end: new GanttDate().endOfYear().endOfWeek({ weekStartsOn: 1 }),
addAmount: 1,
- addUnit: 'month',
- fillDays: 1
+ addUnit: 'month'
};
export class GanttViewWeek extends GanttView {
@@ -19,11 +18,11 @@ export class GanttViewWeek extends GanttView {
super(start, end, Object.assign({}, viewOptions, options));
}
- startOf(date: GanttDate) {
+ viewStartOf(date: GanttDate) {
return date.startOfWeek({ weekStartsOn: 1 });
}
- endOf(date: GanttDate) {
+ viewEndOf(date: GanttDate) {
return date.endOfWeek({ weekStartsOn: 1 });
}
diff --git a/packages/gantt/src/views/year.ts b/packages/gantt/src/views/year.ts
index 039463bb..e4825fe4 100644
--- a/packages/gantt/src/views/year.ts
+++ b/packages/gantt/src/views/year.ts
@@ -9,8 +9,7 @@ const viewOptions: GanttViewOptions = {
start: new GanttDate().addYears(-2).startOfYear(),
end: new GanttDate().addYears(2).endOfYear(),
addAmount: 1,
- addUnit: 'year',
- fillDays: 30
+ addUnit: 'year'
};
export class GanttViewYear extends GanttView {
@@ -20,11 +19,11 @@ export class GanttViewYear extends GanttView {
super(start, end, Object.assign({}, viewOptions, options));
}
- startOf(date: GanttDate) {
+ viewStartOf(date: GanttDate) {
return date.startOfYear();
}
- endOf(date: GanttDate) {
+ viewEndOf(date: GanttDate) {
return date.endOfYear();
}