devexpress-gantt
Version:
DevExpress Gantt Control
448 lines (421 loc) • 18.7 kB
text/typescript
import { Size } from "@devexpress/utils/lib/geometry/size";
import { DomUtils } from "@devexpress/utils/lib/utils/dom";
import { ITaskAreaContainer } from "../../Interfaces/ITaskAreaContainer";
import { DateRange } from "../../Model/WorkingTime/DateRange";
import { GanttView } from "../../View/GanttView";
import { ViewType } from "../../View/Helpers/Enums";
import { GridElementInfo } from "../../View/Helpers/GridElementInfo";
import { GridLayoutCalculator } from "../../View/Helpers/GridLayoutCalculator";
import { Settings } from "../../View/Settings/Settings";
import { DateUtils } from "../../View/Utils/DateUtils";
import { IPdfExportRangeProps } from "./Settings/Props";
import { Color } from "./Table/Color";
import { DataExportMode } from "./Settings/Enums";
import { PdfDataRange } from "./Settings/DataRange";
export class TaskAreaExportHelper {
private _owner: GanttView;
private _visibleLeft: number;
private _visibleRight: number;
private _visibleTop: number;
private _visibleBottom: number;
private _scaleLeft: number;
private _scaleRight: number;
private _scaleTopWidths: Array<number>;
private _scaleBottomWidths: Array<number>;
private _scaleRanges: number[][];
private _offsetLeft: number;
private _offsetTop: number;
private _headerRowHeight: number;
private _arrowWidth: number;
private _customRangeLeftOffset: number;
private _visibleTaskIndices: number[];
private _visibleDependencyKeys: string[];
private _connectorLines: Array<GridElementInfo>;
private _resourcesElements: Array<HTMLElement>;
private _stripLinesElements: Array<HTMLElement>;
private _noWorkingIntervalsElements: Array<HTMLElement>;
private _scaleTableStyle: Record<string, any>;
private _chartMainTableStyle: Record<string, any>;
private _parentRowBackColor: string;
private _dependencyColor: string;
private _props: IPdfExportRangeProps;
private _layoutCalculator: GridLayoutCalculator;
constructor(owner: GanttView, props: IPdfExportRangeProps) {
this._owner = owner;
this._props = props;
}
public get customRangeLeftOffset(): number {
this._customRangeLeftOffset ??= this.layoutCalculator.getWidthByDateRange(this.startDate, this.ownerStartDate);
return this._customRangeLeftOffset;
}
public get baseCellSize(): Size { return this._owner.tickSize; }
public get objectsLeftDelta(): number {
return this.renderedScaleLeft;
}
public get objectsTopDelta(): number {
if(!this.hasTasks)
return 0;
const firstIndex = this.visibleTaskIndices[0];
return this.getCellTop(firstIndex) + this.getTaskCellOffsetTop(firstIndex);
}
public get offsetLeft(): number {
this._offsetLeft ??= Math.max(this.visibleLeft - this.renderedScaleLeft, 0);
return this._offsetLeft;
}
public get offsetTop(): number {
this._offsetTop ??= this.getOffsetTop();
return this._offsetTop;
}
public get scales(): number[] {
const viewType = this.settings.viewType;
return [DateUtils.ViewTypeToScaleMap[viewType], viewType];
}
public get scaleRanges(): number[][] {
this._scaleRanges ??= this.layoutCalculator.getScaleRangesInArea(this.scaleLeft, this.scaleRight);
return this._scaleRanges;
}
public get scaleBottomStartIndex(): number {
return this.scaleRanges[1][0];
}
public get scaleBottomEndIndex(): number {
return this.scaleRanges[1][1];
}
public get scaleTopStartIndex(): number {
return this.scaleRanges[0][0];
}
public get scaleTopEndIndex(): number {
return this.scaleRanges[0][1];
}
public get scaleTopWidths(): Array<number> {
this._scaleTopWidths ??= this.getScaleTopWidths();
return this._scaleTopWidths;
}
public get scaleBottomWidths(): Array<number> {
this._scaleBottomWidths ??= this.getScaleBottomWidths();
return this._scaleBottomWidths;
}
public get headerRowHeight(): number {
if(!this._headerRowHeight) {
const element = this.scaleElements[0].filter(el => !!el && el.offsetHeight)[0];
this._headerRowHeight = element?.offsetHeight;
}
return this._headerRowHeight;
}
public get visibleTaskIndices(): number[] {
this._visibleTaskIndices ??= this.getTaskIndices();
return this._visibleTaskIndices;
}
public get baseCellHeight(): number {
return this.hasTasks ? this.baseCellSize.height : this.taskAreaHeight;
}
public get taskAreaHeight(): number {
return this.hasTasks ? this.visibleTaskIndices.length * this.baseCellHeight : this._owner.renderHelper.taskArea.offsetHeight;
}
public get scaleTableStyle(): Record<string, any> {
this._scaleTableStyle ??= this.getScaleTableStyle();
return this._scaleTableStyle;
}
public get chartMainTableStyle(): Record<string, any> {
this._chartMainTableStyle ??= this.getChartMainTableStyle();
return this._chartMainTableStyle;
}
public get parentRowBackColor(): string {
this._parentRowBackColor ??= this.getParentBackColor();
return this._parentRowBackColor;
}
public get arrowWidth(): number {
this._arrowWidth ??= this.getArrowWidth();
return this._arrowWidth;
}
public get dependencyColor(): string {
this._dependencyColor ??= this.getDependencyColor();
return this._dependencyColor;
}
public getTaskElementBackColor(taskIndex: number, className: string): string {
const style = this.getTaskElementStyle(taskIndex, className);
return style?.backgroundColor;
}
public getTaskElementStyle(taskIndex: number, className: string): CSSStyleDeclaration {
const taskWrapper = this.getTaskWrapper(taskIndex);
return this.getElementStyle(taskWrapper.getElementsByClassName(className)[0]);
}
public isElementVisible(element: Element): boolean {
return element && getComputedStyle(element).display !== "none";
}
protected get hasTasks(): boolean {
return this.visibleTaskIndices.length > 0;
}
protected get visibleLeft(): number {
this._visibleLeft ??= this.isVisibleMode ? this.container.scrollLeft : 0;
return this._visibleLeft;
}
protected get visibleTop(): number {
this._visibleTop ??= this.isVisibleMode ? this.container.scrollTop : 0;
return this._visibleTop;
}
protected get visibleRight(): number {
this._visibleRight ??= this.getVisibleRight();
return this._visibleRight;
}
protected getVisibleRight(): number {
const width = this.container.getElement().offsetWidth;
return this.visibleLeft + width;
}
protected get visibleBottom(): number {
this._visibleBottom ??= this.getVisibleBottom();
return this._visibleBottom;
}
protected getVisibleBottom(): number {
if(!this.isVisibleMode)
return this.visibleTaskIndices.length * this.baseCellSize.height;
return this.visibleTop + this.container.getHeight();
}
protected get scaleLeft(): number {
this._scaleLeft ??= this.isVisibleMode ? this.visibleLeft : this.getPosByDate(this.startDate);
return this._scaleLeft;
}
protected get scaleRight(): number {
this._scaleRight ??= this.isVisibleMode ? this.visibleRight : this.getPosByDate(this.endDate) - 1;
return this._scaleRight;
}
protected getScaleTopWidths(): Array<number> {
const widths = this.getScaleWidths(this.scaleTopStartIndex, this.scaleTopEndIndex, this.scales[0]);
const calc = this.layoutCalculator;
const firstBottom = calc.getScaleItemInfo(this.scaleBottomStartIndex, this.scales[1]);
const firstTop = calc.getScaleItemInfo(this.scaleTopStartIndex, this.scales[0]);
let delta = Math.max(firstBottom.position.x - firstTop.position.x, 0);
widths[this.scaleTopStartIndex] -= delta;
const lastTop = calc.getScaleItemInfo(this.scaleTopEndIndex, this.scales[0]);
const lastBottom = calc.getScaleItemInfo(this.scaleBottomEndIndex, this.scales[1]);
delta = Math.max(lastTop.position.x + lastTop.size.width - lastBottom.position.x - lastBottom.size.width, 0);
widths[this.scaleTopEndIndex] -= delta;
return widths;
}
protected getScaleBottomWidths(): Array<number> {
return this.getScaleWidths(this.scaleBottomStartIndex, this.scaleBottomEndIndex, this.scales[1]);
}
protected getScaleWidths(start: number, end: number, scaleType: ViewType): Array<number> {
const widths = new Array<number>();
for(let i = start; i <= end; i++)
widths[i] = this.layoutCalculator.getScaleItemInfo(i, scaleType).size.width;
return widths;
}
protected getOffsetTop(): number {
return this.isVisibleMode && this.hasTasks ? this.getTaskCellOffsetTop(this.visibleTaskIndices[0]) : 0;
}
protected get renderedScaleLeft(): number {
return this.getCellLeft(this.scaleBottomStartIndex);
}
protected getTaskCellOffsetTop(taskIndex: number): number {
const point = this.getCellTop(taskIndex);
return Math.max(this.visibleTop - point, 0);
}
protected getCellTop(index: number): number {
const point = this.layoutCalculator.getGridBorderPosition(index - 1, false);
return point.y;
}
protected getCellLeft(index: number): number {
const point = this.layoutCalculator.getScaleItemInfo(index, this.scales[1]).position;
return point.x;
}
protected getTaskIndices(): Array<number> {
if(this.dataMode === DataExportMode.all || this.exportRange)
return this._owner.getAllVisibleTaskIndices(this.exportRange?.startIndex, this.exportRange?.endIndex);
return this.getVisibleTaskIndices();
}
protected getVisibleTaskIndices(): Array<number> {
const indices = [];
this.taskElements.forEach((t, i) => {
if(t) {
const taskTop = DomUtils.pxToInt(t.style.top);
const taskBottom = taskTop + t.offsetHeight;
const topInArea = taskTop >= this.visibleTop && taskTop <= this.visibleBottom;
const bottomInArea = taskBottom >= this.visibleTop && taskBottom <= this.visibleBottom;
if(topInArea || bottomInArea)
indices.push(i);
}
});
return indices;
}
protected get scaleElements(): Array<Array<HTMLElement>> {
return this._owner.renderHelper.scaleElements.slice();
}
protected get scaleBorders(): Array<Array<HTMLElement>> {
return this._owner.renderHelper.scaleBorders.slice();
}
protected get hlRowElements(): Array<HTMLElement> {
return this._owner.renderHelper.hlRowElements.filter(el => !!el);
}
protected get selectionElements(): Array<HTMLElement> {
return this._owner.renderHelper.selectionElements.filter(el => !!el);
}
protected get taskElements(): Array<HTMLElement> {
return this._owner.renderHelper.taskElements;
}
public get connectorLines(): Array<GridElementInfo> {
this._connectorLines ??= this._owner.renderHelper.allConnectorLines.filter(l => this.isLineVisible(l));
return this._connectorLines;
}
protected isLineVisible(line: GridElementInfo): boolean {
if(this.dataMode === DataExportMode.all)
return true;
const id = line.attr["dependency-id"];
return this.visibleDependencyKeys.indexOf(id) > -1;
}
protected get visibleDependencyKeys(): string[] {
this._visibleDependencyKeys ??= this._owner.getVisibleDependencyKeysByTaskRange(this.visibleTaskIndices);
return this._visibleDependencyKeys;
}
public get resourcesElements(): Array<HTMLElement> {
this._resourcesElements ??= this.visibleTaskIndices.map(tIndex => this._owner.renderHelper.resourcesElements[tIndex]).filter(r => this.isElementVisible(r) && r.parentElement);
return this._resourcesElements;
}
public get stripLinesElements(): Array<HTMLElement> {
if(!this._stripLinesElements) {
const elements = this._owner.renderHelper.stripLinesMap.filter(s =>!!s).map(s => s as unknown);
this._stripLinesElements = elements.map(e => e as HTMLElement);
}
return this._stripLinesElements;
}
public get noWorkingIntervalsElements(): Array<HTMLElement> {
if(!this._noWorkingIntervalsElements) {
this._noWorkingIntervalsElements = [ ];
const hash = this._owner.renderHelper.noWorkingIntervalsToElementsMap;
for(const key in hash) {
if(!Object.prototype.hasOwnProperty.call(hash, key))
continue;
this._noWorkingIntervalsElements.push(hash[key]);
}
}
return this._noWorkingIntervalsElements;
}
protected get taskArea(): HTMLElement {
return this._owner.renderHelper.taskArea;
}
protected get settings(): Settings { return this._owner.settings; }
protected get dataMode(): DataExportMode {
return this._props?.exportDataMode;
}
protected get exportRange(): PdfDataRange {
return this._props?.dateRange;
}
protected get isVisibleMode(): boolean {
return this.dataMode === DataExportMode.visible && !this.exportRange;
}
protected get ownerStartDate(): Date {
return this._owner.range.start;
}
protected get ownerEndDate(): Date {
return this._owner.range.end;
}
protected get startDate(): Date {
if(this.exportRange?.startDate && this.exportRange?.endDate) {
const min = Math.min(this.exportRange?.startDate.getTime(), this.exportRange?.endDate.getTime());
return new Date(min);
}
return this.ownerStartDate;
}
protected get endDate(): Date {
if(this.exportRange?.startDate && this.exportRange?.endDate) {
const max = Math.max(this.exportRange?.startDate.getTime(), this.exportRange?.endDate.getTime());
return new Date(max);
}
return this.ownerEndDate;
}
protected get hasCustomRangeOutOfRender(): boolean {
return this.startDate.getTime() !== this.ownerStartDate.getTime() || this.endDate.getTime() !== this.ownerEndDate.getTime();
}
public get layoutCalculator(): GridLayoutCalculator {
if(!this._layoutCalculator) {
const calc = this._owner.renderHelper.gridLayoutCalculator;
if(this.hasCustomRangeOutOfRender) {
this._layoutCalculator = new GridLayoutCalculator();
this._layoutCalculator.setSettings(
calc.visibleTaskAreaSize,
calc.tickSize,
calc.elementSizeValues,
new DateRange(this.startDate, this.endDate),
calc.viewModel,
calc.viewType,
calc.scrollBarHeight,
this._owner.settings.firstDayOfWeek
);
}
else
this._layoutCalculator = calc;
}
return this._layoutCalculator;
}
protected get container(): ITaskAreaContainer {
return this._owner.renderHelper.taskAreaContainer;
}
protected getPosByDate(date: Date): number {
return this.layoutCalculator.getPosByDate(date);
}
protected getScaleTableStyle(): Record<string, any> {
const result = { };
const visibleScaleItem = this.scaleElements[0].filter(el => !!el)[0];
const style = this.getElementStyle(visibleScaleItem);
result["backgroundColor"] = this.findElementBackColor(visibleScaleItem);
result["borderColor"] = this.getChartTableBorderColor();
result["verticalAlign"] = "middle";
result["textAlign"] = "center";
result["fontSize"] = style.fontSize;
result["fontFamily"] = style.fontFamily;
result["fontWeight"] = style.fontWeight;
result["fontStyle"] = style.fontStyle;
result["color"] = style.color;
return result;
}
protected getChartMainTableStyle(): Record<string, any> {
const result = { };
result["backgroundColor"] = this.findElementBackColor(this.taskArea);
result["borderColor"] = this.getChartTableBorderColor();
return result;
}
protected findElementBackColor(element: Element): Color {
if(!element)
return null;
let parent = element;
const color = new Color("transparent");
while(color.opacity === 0 && parent) {
const style = this.getElementStyle(parent);
color.assign(style.backgroundColor);
parent = parent.parentElement;
}
return color;
}
protected getChartTableBorderColor(): string {
const style = this.getElementStyle(this.scaleBorders[0].filter(el => !!el)[0]);
return style?.borderColor;
}
protected getParentBackColor(): string {
const style = this.getElementStyle(this.hlRowElements[0]);
return style?.backgroundColor;
}
protected getArrowWidth(): number {
const style = this.getDependencyLineStyle(GridLayoutCalculator.arrowClassName);
const borderWidth = style.borderWidth || style.borderLeftWidth || style.borderRightWidth || style.borderTopWidth || style.borderBottomWidth;
return style && DomUtils.pxToInt(borderWidth);
}
protected getDependencyColor(): string {
const style = this.getDependencyLineStyle(GridLayoutCalculator.CLASSNAMES.CONNECTOR_HORIZONTAL);
return style?.borderColor;
}
protected getDependencyLineStyle(className: string): CSSStyleDeclaration {
return this.getElementStyle(this.taskArea.getElementsByClassName(className)[0]);
}
protected getElementStyle(element: Element): CSSStyleDeclaration {
return element && getComputedStyle(element);
}
protected getTaskWrapper(index: number): HTMLElement {
if(this.isTaskTemplateMode)
return this._owner.renderHelper.fakeTaskWrapper;
if(!this.taskElements[index])
this._owner.renderHelper.createDefaultTaskElement(index);
return this.taskElements[index];
}
protected get isTaskTemplateMode(): boolean {
return !!this._owner.settings.taskContentTemplate;
}
}