| /* |
| * 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, cellSize[orient === 'horizontal' ? 0 : 1]); |
| |
| 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; |