/*
|
* Licensed to the Apache Software Foundation (ASF) under one
|
* or more contributor license agreements. See the NOTICE file
|
* distributed with this work for additional information
|
* regarding copyright ownership. The ASF licenses this file
|
* to you under the Apache License, Version 2.0 (the
|
* "License"); you may not use this file except in compliance
|
* with the License. You may obtain a copy of the License at
|
*
|
* http://www.apache.org/licenses/LICENSE-2.0
|
*
|
* Unless required by applicable law or agreed to in writing,
|
* software distributed under the License is distributed on an
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
* KIND, either express or implied. See the License for the
|
* specific language governing permissions and limitations
|
* under the License.
|
*/
|
|
import * as zrUtil from 'zrender/src/core/util';
|
import * as graphic from '../../util/graphic';
|
import {createTextStyle} from '../../label/labelStyle';
|
import * as formatUtil from '../../util/format';
|
import * as numberUtil from '../../util/number';
|
import CalendarModel from '../../coord/calendar/CalendarModel';
|
import {CalendarParsedDateRangeInfo, CalendarParsedDateInfo} from '../../coord/calendar/Calendar';
|
import GlobalModel from '../../model/Global';
|
import ExtensionAPI from '../../core/ExtensionAPI';
|
import { LayoutOrient, OptionDataValueDate, ZRTextAlign, ZRTextVerticalAlign } from '../../util/types';
|
import ComponentView from '../../view/Component';
|
import { PathStyleProps } from 'zrender/src/graphic/Path';
|
import { TextStyleProps, TextProps } from 'zrender/src/graphic/Text';
|
|
const MONTH_TEXT = {
|
EN: [
|
'Jan', 'Feb', 'Mar',
|
'Apr', 'May', 'Jun',
|
'Jul', 'Aug', 'Sep',
|
'Oct', 'Nov', 'Dec'
|
],
|
CN: [
|
'一月', '二月', '三月',
|
'四月', '五月', '六月',
|
'七月', '八月', '九月',
|
'十月', '十一月', '十二月'
|
]
|
};
|
|
const WEEK_TEXT = {
|
EN: ['S', 'M', 'T', 'W', 'T', 'F', 'S'],
|
CN: ['日', '一', '二', '三', '四', '五', '六']
|
};
|
|
class CalendarView extends ComponentView {
|
|
static type = 'calendar';
|
type = CalendarView.type;
|
|
/**
|
* top/left line points
|
*/
|
private _tlpoints: number[][];
|
|
/**
|
* bottom/right line points
|
*/
|
private _blpoints: number[][];
|
|
/**
|
* first day of month
|
*/
|
private _firstDayOfMonth: CalendarParsedDateInfo[];
|
|
/**
|
* first day point of month
|
*/
|
private _firstDayPoints: number[][];
|
|
render(calendarModel: CalendarModel, ecModel: GlobalModel, api: ExtensionAPI) {
|
|
const group = this.group;
|
|
group.removeAll();
|
|
const coordSys = calendarModel.coordinateSystem;
|
|
// range info
|
const rangeData = coordSys.getRangeInfo();
|
const orient = coordSys.getOrient();
|
|
this._renderDayRect(calendarModel, rangeData, group);
|
|
// _renderLines must be called prior to following function
|
this._renderLines(calendarModel, rangeData, orient, group);
|
|
this._renderYearText(calendarModel, rangeData, orient, group);
|
|
this._renderMonthText(calendarModel, orient, group);
|
|
this._renderWeekText(calendarModel, rangeData, orient, group);
|
}
|
|
// render day rect
|
_renderDayRect(calendarModel: CalendarModel, rangeData: CalendarParsedDateRangeInfo, group: graphic.Group) {
|
const coordSys = calendarModel.coordinateSystem;
|
const itemRectStyleModel = calendarModel.getModel('itemStyle').getItemStyle();
|
const sw = coordSys.getCellWidth();
|
const sh = coordSys.getCellHeight();
|
|
for (let i = rangeData.start.time;
|
i <= rangeData.end.time;
|
i = coordSys.getNextNDay(i, 1).time
|
) {
|
|
const point = coordSys.dataToRect([i], false).tl;
|
|
// every rect
|
const rect = new graphic.Rect({
|
shape: {
|
x: point[0],
|
y: point[1],
|
width: sw,
|
height: sh
|
},
|
cursor: 'default',
|
style: itemRectStyleModel
|
});
|
|
group.add(rect);
|
}
|
|
}
|
|
// render separate line
|
_renderLines(
|
calendarModel: CalendarModel,
|
rangeData: CalendarParsedDateRangeInfo,
|
orient: LayoutOrient,
|
group: graphic.Group
|
) {
|
|
const self = this;
|
|
const coordSys = calendarModel.coordinateSystem;
|
|
const lineStyleModel = calendarModel.getModel(['splitLine', 'lineStyle']).getLineStyle();
|
const show = calendarModel.get(['splitLine', 'show']);
|
|
const lineWidth = lineStyleModel.lineWidth;
|
|
this._tlpoints = [];
|
this._blpoints = [];
|
this._firstDayOfMonth = [];
|
this._firstDayPoints = [];
|
|
|
let firstDay = rangeData.start;
|
|
for (let i = 0; firstDay.time <= rangeData.end.time; i++) {
|
addPoints(firstDay.formatedDate);
|
|
if (i === 0) {
|
firstDay = coordSys.getDateInfo(rangeData.start.y + '-' + rangeData.start.m);
|
}
|
|
const date = firstDay.date;
|
date.setMonth(date.getMonth() + 1);
|
firstDay = coordSys.getDateInfo(date);
|
}
|
|
addPoints(coordSys.getNextNDay(rangeData.end.time, 1).formatedDate);
|
|
function addPoints(date: OptionDataValueDate) {
|
|
self._firstDayOfMonth.push(coordSys.getDateInfo(date));
|
self._firstDayPoints.push(coordSys.dataToRect([date], false).tl);
|
|
const points = self._getLinePointsOfOneWeek(calendarModel, date, orient);
|
|
self._tlpoints.push(points[0]);
|
self._blpoints.push(points[points.length - 1]);
|
|
show && self._drawSplitline(points, lineStyleModel, group);
|
}
|
|
|
// render top/left line
|
show && this._drawSplitline(self._getEdgesPoints(self._tlpoints, lineWidth, orient), lineStyleModel, group);
|
|
// render bottom/right line
|
show && this._drawSplitline(self._getEdgesPoints(self._blpoints, lineWidth, orient), lineStyleModel, group);
|
|
}
|
|
// get points at both ends
|
_getEdgesPoints(points: number[][], lineWidth: number, orient: LayoutOrient) {
|
const rs = [points[0].slice(), points[points.length - 1].slice()];
|
const idx = orient === 'horizontal' ? 0 : 1;
|
|
// both ends of the line are extend half lineWidth
|
rs[0][idx] = rs[0][idx] - lineWidth / 2;
|
rs[1][idx] = rs[1][idx] + lineWidth / 2;
|
|
return rs;
|
}
|
|
// render split line
|
_drawSplitline(points: number[][], lineStyle: PathStyleProps, group: graphic.Group) {
|
|
const poyline = new graphic.Polyline({
|
z2: 20,
|
shape: {
|
points: points
|
},
|
style: lineStyle
|
});
|
|
group.add(poyline);
|
}
|
|
// render month line of one week points
|
_getLinePointsOfOneWeek(calendarModel: CalendarModel, date: OptionDataValueDate, orient: LayoutOrient) {
|
|
const coordSys = calendarModel.coordinateSystem;
|
const parsedDate = coordSys.getDateInfo(date);
|
|
const points = [];
|
|
for (let i = 0; i < 7; i++) {
|
|
const tmpD = coordSys.getNextNDay(parsedDate.time, i);
|
const point = coordSys.dataToRect([tmpD.time], false);
|
|
points[2 * tmpD.day] = point.tl;
|
points[2 * tmpD.day + 1] = point[orient === 'horizontal' ? 'bl' : 'tr'];
|
}
|
|
return points;
|
|
}
|
|
_formatterLabel<T extends { nameMap: string }>(
|
formatter: string | ((params: T) => string),
|
params: T
|
) {
|
|
if (typeof formatter === 'string' && formatter) {
|
return formatUtil.formatTplSimple(formatter, params);
|
}
|
|
if (typeof formatter === 'function') {
|
return formatter(params);
|
}
|
|
return params.nameMap;
|
|
}
|
|
_yearTextPositionControl(
|
textEl: graphic.Text,
|
point: number[],
|
orient: LayoutOrient,
|
position: 'left' | 'right' | 'top' | 'bottom',
|
margin: number
|
): TextProps {
|
|
let x = point[0];
|
let y = point[1];
|
let aligns: [ZRTextAlign, ZRTextVerticalAlign] = ['center', 'bottom'];
|
|
if (position === 'bottom') {
|
y += margin;
|
aligns = ['center', 'top'];
|
}
|
else if (position === 'left') {
|
x -= margin;
|
}
|
else if (position === 'right') {
|
x += margin;
|
aligns = ['center', 'top'];
|
}
|
else { // top
|
y -= margin;
|
}
|
|
let rotate = 0;
|
if (position === 'left' || position === 'right') {
|
rotate = Math.PI / 2;
|
}
|
|
return {
|
rotation: rotate,
|
x,
|
y,
|
style: {
|
align: aligns[0],
|
verticalAlign: aligns[1]
|
}
|
};
|
}
|
|
// render year
|
_renderYearText(
|
calendarModel: CalendarModel,
|
rangeData: CalendarParsedDateRangeInfo,
|
orient: LayoutOrient,
|
group: graphic.Group
|
) {
|
const yearLabel = calendarModel.getModel('yearLabel');
|
|
if (!yearLabel.get('show')) {
|
return;
|
}
|
|
const margin = yearLabel.get('margin');
|
let pos = yearLabel.get('position');
|
|
if (!pos) {
|
pos = orient !== 'horizontal' ? 'top' : 'left';
|
}
|
|
const points = [this._tlpoints[this._tlpoints.length - 1], this._blpoints[0]];
|
const xc = (points[0][0] + points[1][0]) / 2;
|
const yc = (points[0][1] + points[1][1]) / 2;
|
|
const idx = orient === 'horizontal' ? 0 : 1;
|
|
const posPoints = {
|
top: [xc, points[idx][1]],
|
bottom: [xc, points[1 - idx][1]],
|
left: [points[1 - idx][0], yc],
|
right: [points[idx][0], yc]
|
};
|
|
let name = rangeData.start.y;
|
|
if (+rangeData.end.y > +rangeData.start.y) {
|
name = name + '-' + rangeData.end.y;
|
}
|
|
const formatter = yearLabel.get('formatter');
|
|
const params = {
|
start: rangeData.start.y,
|
end: rangeData.end.y,
|
nameMap: name
|
};
|
|
const content = this._formatterLabel(formatter, params);
|
|
const yearText = new graphic.Text({
|
z2: 30,
|
style: createTextStyle(yearLabel, {
|
text: content
|
})
|
});
|
yearText.attr(this._yearTextPositionControl(yearText, posPoints[pos], orient, pos, margin));
|
|
group.add(yearText);
|
}
|
|
_monthTextPositionControl(
|
point: number[],
|
isCenter: boolean,
|
orient: LayoutOrient,
|
position: 'start' | 'end',
|
margin: number
|
): TextStyleProps {
|
let align: ZRTextAlign = 'left';
|
let vAlign: ZRTextVerticalAlign = 'top';
|
let x = point[0];
|
let y = point[1];
|
|
if (orient === 'horizontal') {
|
y = y + margin;
|
|
if (isCenter) {
|
align = 'center';
|
}
|
|
if (position === 'start') {
|
vAlign = 'bottom';
|
}
|
}
|
else {
|
x = x + margin;
|
|
if (isCenter) {
|
vAlign = 'middle';
|
}
|
|
if (position === 'start') {
|
align = 'right';
|
}
|
}
|
|
return {
|
x: x,
|
y: y,
|
align: align,
|
verticalAlign: vAlign
|
};
|
}
|
|
// render month and year text
|
_renderMonthText(calendarModel: CalendarModel, orient: LayoutOrient, group: graphic.Group) {
|
const monthLabel = calendarModel.getModel('monthLabel');
|
|
if (!monthLabel.get('show')) {
|
return;
|
}
|
|
let nameMap = monthLabel.get('nameMap');
|
let margin = monthLabel.get('margin');
|
const pos = monthLabel.get('position');
|
const align = monthLabel.get('align');
|
|
const termPoints = [this._tlpoints, this._blpoints];
|
|
if (zrUtil.isString(nameMap)) {
|
nameMap = MONTH_TEXT[nameMap.toUpperCase() as 'CN' | 'EN'] || [];
|
}
|
|
const idx = pos === 'start' ? 0 : 1;
|
const axis = orient === 'horizontal' ? 0 : 1;
|
margin = pos === 'start' ? -margin : margin;
|
const isCenter = (align === 'center');
|
|
for (let i = 0; i < termPoints[idx].length - 1; i++) {
|
|
const tmp = termPoints[idx][i].slice();
|
const firstDay = this._firstDayOfMonth[i];
|
|
if (isCenter) {
|
const firstDayPoints = this._firstDayPoints[i];
|
tmp[axis] = (firstDayPoints[axis] + termPoints[0][i + 1][axis]) / 2;
|
}
|
|
const formatter = monthLabel.get('formatter');
|
const name = nameMap[+firstDay.m - 1];
|
const params = {
|
yyyy: firstDay.y,
|
yy: (firstDay.y + '').slice(2),
|
MM: firstDay.m,
|
M: +firstDay.m,
|
nameMap: name
|
};
|
|
const content = this._formatterLabel(formatter, params);
|
|
const monthText = new graphic.Text({
|
z2: 30,
|
style: zrUtil.extend(
|
createTextStyle(monthLabel, {text: content}),
|
this._monthTextPositionControl(tmp, isCenter, orient, pos, margin)
|
)
|
});
|
|
group.add(monthText);
|
}
|
}
|
|
_weekTextPositionControl(
|
point: number[],
|
orient: LayoutOrient,
|
position: 'start' | 'end',
|
margin: number,
|
cellSize: number[]
|
): TextStyleProps {
|
let align: ZRTextAlign = 'center';
|
let vAlign: ZRTextVerticalAlign = 'middle';
|
let x = point[0];
|
let y = point[1];
|
const isStart = position === 'start';
|
|
if (orient === 'horizontal') {
|
x = x + margin + (isStart ? 1 : -1) * cellSize[0] / 2;
|
align = isStart ? 'right' : 'left';
|
}
|
else {
|
y = y + margin + (isStart ? 1 : -1) * cellSize[1] / 2;
|
vAlign = isStart ? 'bottom' : 'top';
|
}
|
|
return {
|
x: x,
|
y: y,
|
align: align,
|
verticalAlign: vAlign
|
};
|
}
|
|
// render weeks
|
_renderWeekText(
|
calendarModel: CalendarModel,
|
rangeData: CalendarParsedDateRangeInfo,
|
orient: LayoutOrient,
|
group: graphic.Group
|
) {
|
const dayLabel = calendarModel.getModel('dayLabel');
|
|
if (!dayLabel.get('show')) {
|
return;
|
}
|
|
const coordSys = calendarModel.coordinateSystem;
|
const pos = dayLabel.get('position');
|
let nameMap = dayLabel.get('nameMap');
|
let margin = dayLabel.get('margin');
|
const firstDayOfWeek = coordSys.getFirstDayOfWeek();
|
|
if (zrUtil.isString(nameMap)) {
|
nameMap = WEEK_TEXT[nameMap.toUpperCase() as 'CN' | 'EN'] || [];
|
}
|
|
let start = coordSys.getNextNDay(
|
rangeData.end.time, (7 - rangeData.lweek)
|
).time;
|
|
const cellSize = [coordSys.getCellWidth(), coordSys.getCellHeight()];
|
margin = numberUtil.parsePercent(margin, Math.min(cellSize[1], cellSize[0]));
|
|
if (pos === 'start') {
|
start = coordSys.getNextNDay(
|
rangeData.start.time, -(7 + rangeData.fweek)
|
).time;
|
margin = -margin;
|
}
|
|
for (let i = 0; i < 7; i++) {
|
|
const tmpD = coordSys.getNextNDay(start, i);
|
const point = coordSys.dataToRect([tmpD.time], false).center;
|
let day = i;
|
day = Math.abs((i + firstDayOfWeek) % 7);
|
const weekText = new graphic.Text({
|
z2: 30,
|
style: zrUtil.extend(
|
createTextStyle(dayLabel, {text: nameMap[day]}),
|
this._weekTextPositionControl(point, orient, pos, margin, cellSize)
|
)
|
});
|
|
group.add(weekText);
|
}
|
}
|
}
|
|
export default CalendarView;
|