hqchart
Version:
HQChart - H5, 微信小程序 沪深/港股/数字货币/期货/美股 K线图(kline),走势图,缩放,拖拽,十字光标,画图工具,截图,筹码图. 分析家语法,通达信语法,(麦语法),第3方数据对接
1,532 lines (1,248 loc) • 61.2 kB
JavaScript
/*
Copyright (c) 2018 jones
http://www.apache.org/licenses/LICENSE-2.0
开源项目 https://github.com/jones2000/HQChart
jones_2000@163.com
封装日历控件 (H5版本)
使用新的class语法
*/
class JSCalendarChart
{
DivElement=null;
JSChartContainer=null; //表格控件
ResizeListener=null; //大小变动监听
CanvasElement=null;
constructor(divElement)
{
this.DivElement=divElement;
//h5 canvas
this.CanvasElement=document.createElement("canvas");
this.CanvasElement.className='jsorderlist-drawing';
this.CanvasElement.id=Guid();
this.CanvasElement.setAttribute("tabindex",0);
if (this.CanvasElement.style) this.CanvasElement.style.outline='none';
if(divElement.hasChildNodes())
{
JSConsole.Chart.Log("[JSCalendarChart::JSOrderChart] divElement hasChildNodes", divElement.childNodes);
}
divElement.appendChild(this.CanvasElement);
}
OnSize()
{
//画布大小通过div获取
var height=this.DivElement.offsetHeight;
var width=this.DivElement.offsetWidth;
if (this.DivElement.style.height && this.DivElement.style.width)
{
if (this.DivElement.style.height.includes("px"))
height=parseInt(this.DivElement.style.height.replace("px",""));
if (this.DivElement.style.width.includes("px"))
width=parseInt(this.DivElement.style.width.replace("px",""));
}
this.CanvasElement.height=height;
this.CanvasElement.width=width;
this.CanvasElement.style.width=this.CanvasElement.width+'px';
this.CanvasElement.style.height=this.CanvasElement.height+'px';
var pixelTatio = GetDevicePixelRatio(); //获取设备的分辨率
this.CanvasElement.height*=pixelTatio;
this.CanvasElement.width*=pixelTatio;
JSConsole.Chart.Log(`[JSCalendarChart::OnSize] devicePixelRatio=${window.devicePixelRatio}, height=${this.CanvasElement.height}, width=${this.CanvasElement.width}`);
if (this.JSChartContainer && this.JSChartContainer.OnSize)
{
this.JSChartContainer.OnSize();
}
}
SetOption(option)
{
var chart=this.CreateJSCalendarChartContainer(option);
if (!chart) return false;
if (option.OnCreatedCallback) option.OnCreatedCallback(chart);
this.JSChartContainer=chart;
this.DivElement.JSChart=this; //div中保存一份
//注册事件
if (IFrameSplitOperator.IsNonEmptyArray(option.EventCallback))
{
for(var i=0;i<option.EventCallback.length;++i)
{
var item=option.EventCallback[i];
chart.AddEventCallback(item);
}
}
if (option.EnableResize==true) this.CreateResizeListener();
//if (option.FloatTooltip && option.FloatTooltip.Enable) chart.InitalFloatTooltip(option.FloatTooltip); //提示信息
chart.GotoToday();
chart.Draw();
}
CreateResizeListener()
{
this.ResizeListener = new ResizeObserver((entries)=>{ this.OnDivResize(entries); });
this.ResizeListener.observe(this.DivElement);
}
OnDivResize=function(entries)
{
JSConsole.Chart.Log("[JSCalendarChart::OnDivResize] entries=", entries);
this.OnSize();
}
CreateJSCalendarChartContainer(option)
{
var chart=new JSCalendarChartContainer(this.CanvasElement);
chart.Create(option);
if (option.NetworkFilter) chart.NetworkFilter=option.NetworkFilter;
this.SetChartBorder(chart, option);
//是否自动更新
if (option.IsAutoUpdate!=null) chart.IsAutoUpdate=option.IsAutoUpdate;
if (option.AutoUpdateFrequency>0) chart.AutoUpdateFrequency=option.AutoUpdateFrequency;
var calendarChart=chart.GetCalendarChart();
//注册事件
if (option.EventCallback)
{
for(var i=0;i<option.EventCallback.length;++i)
{
var item=option.EventCallback[i];
chart.AddEventCallback(item);
}
}
return chart;
}
SetChartBorder(chart, option)
{
if (!option.Border) return;
var item=option.Border;
if (IFrameSplitOperator.IsNumber(option.Border.Left)) chart.Frame.ChartBorder.Left=option.Border.Left;
if (IFrameSplitOperator.IsNumber(option.Border.Right)) chart.Frame.ChartBorder.Right=option.Border.Right;
if (IFrameSplitOperator.IsNumber(option.Border.Top)) chart.Frame.ChartBorder.Top=option.Border.Top;
if (IFrameSplitOperator.IsNumber(option.Border.Bottom)) chart.Frame.ChartBorder.Bottom=option.Border.Bottom;
var pixelTatio = GetDevicePixelRatio(); //获取设备的分辨率
chart.Frame.ChartBorder.Left*=pixelTatio;
chart.Frame.ChartBorder.Right*=pixelTatio;
chart.Frame.ChartBorder.Top*=pixelTatio;
chart.Frame.ChartBorder.Bottom*=pixelTatio;
}
//事件回调
AddEventCallback(obj)
{
if(this.JSChartContainer && typeof(this.JSChartContainer.AddEventCallback)=='function')
{
JSConsole.Chart.Log('[JSCalendarChart:AddEventCallback] obj=', obj);
this.JSChartContainer.AddEventCallback(obj);
}
}
//重新加载配置
ReloadResource(option)
{
if(this.JSChartContainer && typeof(this.JSChartContainer.ReloadResource)=='function')
{
JSConsole.Chart.Log('[JSCalendarChart:ReloadResource] ');
this.JSChartContainer.ReloadResource(option);
}
}
ChartDestroy()
{
if (this.JSChartContainer && typeof (this.JSChartContainer.ChartDestroy) == 'function')
{
this.JSChartContainer.ChartDestroy();
}
}
SetMarkData(aryData, opiton)
{
if (this.JSChartContainer && typeof (this.JSChartContainer.SetMarkData) == 'function')
{
this.JSChartContainer.SetMarkData(aryData, opiton);
}
}
//初始化 静态方法
static Init(divElement)
{
var jsChartControl=new JSCalendarChart(divElement);
jsChartControl.OnSize();
return jsChartControl;
}
}
class JSCalendarChartContainer
{
ClassName='JSCalendarChartContainer';
Frame; //框架画法
ChartPaint=[]; //图形画法
Canvas=null; //画布
NetworkFilter=null; //数据回调接口
Data=
{
Current:{ Year:null, Month:null,}, //标题栏显示年和月份
Days:[ ], // 日期 { Text:, Date:, Week:1, Type: 0 当前月 -1 上个月 1=下个月 }
Months:[], //月份 { Text:"1月", Date:20260100, Month:1 }
MapDayMarks:new Map(), //日期标记 key=date vallue={ Date:, Type, }
};
//事件回调
MapEvent=new Map(); //通知外部调用 key:JSCHART_EVENT_ID value:{Callback:回调,}
AutoUpdateTimer=null;
AutoUpdateFrequency=15000; //更新频率
FloatTooltip=null; //提示浮框
UIElement=null;
LastPoint={ X:null, Y:null }; //鼠标位置
//MouseOnStatus:{ RowIndex:行, ColumnIndex:列}
LastMouseStatus={ MoveStatus:null, TooltipStatus:null, MouseOnStatus:null };
//日期限制
DateLimit={ End:{ Enable:false, Date:null }, Start:{ Enable:false, Date:null } };
IsDestroy=false; //是否已经销毁了
constructor(uielement)
{
this.Canvas=uielement.getContext("2d"); //画布
this.UIElement=uielement;
}
ChartDestroy() //销毁
{
this.IsDestroy=true;
this.StopAutoUpdate();
this.DestroyFloatTooltip();
}
HideFloatTooltip()
{
if (!this.FloatTooltip) return;
this.FloatTooltip.Hide();
}
DestroyFloatTooltip()
{
if (!this.FloatTooltip) return;
this.FloatTooltip.Destroy();
this.FloatTooltip=null;
}
InitalFloatTooltip(option)
{
if (this.FloatTooltip) return;
this.FloatTooltip=new JSFloatTooltip();
this.FloatTooltip.Inital(this, option);
this.FloatTooltip.Create();
}
DrawFloatTooltip(point,toolTip)
{
if (!this.FloatTooltip) return;
this.UpdateFloatTooltip(point, toolTip)
}
HideAllTooltip()
{
this.HideFloatTooltip();
}
//设置事件回调
//{event:事件id, callback:回调函数}
AddEventCallback(object)
{
if (!object || !object.event || !object.callback) return;
var data={Callback:object.callback, Source:object};
this.MapEvent.set(object.event,data);
}
//创建
//windowCount 窗口个数
Create(option)
{
this.UIElement.JSChartContainer=this;
//创建框架
this.Frame=new JSCalendarFrame();
this.Frame.ChartBorder=new ChartBorder();
this.Frame.ChartBorder.UIElement=this.UIElement;
this.Frame.ChartBorder.Top=30;
this.Frame.ChartBorder.Left=5;
this.Frame.ChartBorder.Bottom=20;
this.Frame.Canvas=this.Canvas;
//创建表格
var chart=new ChartCalendar();
chart.Frame=this.Frame;
chart.ChartBorder=this.Frame.ChartBorder;
chart.Canvas=this.Canvas;
chart.UIElement=this.UIElement;
chart.GetEventCallback=(id)=> { return this.GetEventCallback(id); }
chart.Data=this.Data;
chart.DateLimit=this.DateLimit;
chart.SetOption(option);
this.ChartPaint[0]=chart;
if (option)
{
if (IFrameSplitOperator.IsNumber(option.BorderLine)) this.Frame.BorderLine=option.BorderLine; //边框
if (option.GotoBasePage)
{
var item=option.GotoBasePage;
if (IFrameSplitOperator.IsNumber(item.Frequency)) this.GotoBasePageConfig.Frequency=item.Frequency;
if (IFrameSplitOperator.IsBool(item.Enable)) this.GotoBasePageConfig.Enable=item.Enable;
}
if (option.DateLimit)
{
var item=option.DateLimit;
if (item.End)
{
var subItem=item.End;
if (IFrameSplitOperator.IsBool(subItem.Enable)) this.DateLimit.End.Enable=subItem.Enable;
if (subItem.Date===null || IFrameSplitOperator.IsNumber(subItem.Date)) this.DateLimit.End.Date=subItem.Date;
}
if (item.Start)
{
var subItem=item.Start;
if (IFrameSplitOperator.IsBool(subItem.Enable)) this.DateLimit.End.Enable=subItem.Enable;
if (subItem.Date===null || IFrameSplitOperator.IsNumber(subItem.Date)) this.DateLimit.End.Date=subItem.Date;
}
}
}
var bRegisterKeydown=true;
var bRegisterWheel=true;
if (option)
{
if (option.KeyDown===false)
{
bRegisterKeydown=false;
JSConsole.Chart.Log('[JSOrderChartContainer::Create] not register keydown event.');
}
if (option.Wheel===false)
{
bRegisterWheel=false;
JSConsole.Chart.Log('[JSOrderChartContainer::Create] not register wheel event.');
}
if (IFrameSplitOperator.IsBool(option.EnableSelected)) chart.SelectedData.Enable=option.EnableSelected;
}
//if (bRegisterKeydown) this.UIElement.addEventListener("keydown", (e)=>{ this.OnKeyDown(e); }, true); //键盘消息
if (bRegisterWheel) this.UIElement.addEventListener("wheel", (e)=>{ this.OnWheel(e); }, true); //上下滚动消息
this.UIElement.onmousedown=(e)=> { this.UIOnMouseDown(e); }
//this.UIElement.ondblclick=(e)=>{ this.UIOnDblClick(e); }
//this.UIElement.oncontextmenu=(e)=> { this.UIOnContextMenu(e); }
this.UIElement.onmousemove=(e)=>{ this.UIOnMouseMove(e);}
this.UIElement.onmouseout=(e)=>{ this.UIOnMounseOut(e); }
this.UIElement.onmouseleave=(e)=>{ this.UIOnMouseleave(e); }
}
UIOnMouseDown(e)
{
var pixelTatio = GetDevicePixelRatio();
var x = (e.clientX-this.UIElement.getBoundingClientRect().left)*pixelTatio;
var y = (e.clientY-this.UIElement.getBoundingClientRect().top)*pixelTatio;
var chart=this.GetCalendarChart();
if (!chart) return false;
var clickData=chart.OnMouseDown(x,y,e);
if (!clickData) return;
if (clickData.Type===JSCHART_CALENDAR_BUTTON_ID.YEAR_UP_ARROW_ID)
{
this.GotoPreviousYear({ Draw:true });
}
else if (clickData.Type===JSCHART_CALENDAR_BUTTON_ID.YEAR_DOWN_ARROW_ID)
{
this.GotoNextYear({ Draw:true });
}
else if (clickData.Type===JSCHART_CALENDAR_BUTTON_ID.MONTH_UP_ARROW_ID)
{
this.GotoPreviousMonth({ Draw:true });
}
else if (clickData.Type===JSCHART_CALENDAR_BUTTON_ID.MONTH_DOWN_ARROW_ID)
{
this.GotoNextMonth({ Draw:true });
}
else if (clickData.Type===JSCHART_CALENDAR_BUTTON_ID.MONTH_TITLE_ID)
{
chart.ChangeType(1);
var year=this.Data.Current.Year;
this.GotoMulitMonth(year,1, {Draw:true});
}
else if (clickData.Type===JSCHART_CALENDAR_BUTTON_ID.MONTH_CELL_ID)
{
chart.ChangeType(0);
var date=clickData.Data.Date;
var year=parseInt(date/10000);
var month=parseInt(date/100)%100;
this.GotoMonth(year, month, { Draw:true });
}
else if (clickData.Type==JSCHART_CALENDAR_BUTTON_ID.DAY_CELL_ID)
{
var eventID=JSCHART_EVENT_ID.ON_CLICK_CALENDAR_DAY_CELL;
if (e && e.button==2) eventID=JSCHART_EVENT_ID.ON_RCLICK_CALENDAR_DAY_CELL;
var sendData={ Data:clickData.Data, X:x, Y:y, e:e, PreventDefault:false }
this.SendClickEvent(eventID, sendData);
if (sendData.PreventDefault) return;
chart.SetSelectedDay([{Date:clickData.Data.Date}]);
this.Draw();
}
}
GetEventCallback(id) //获取事件回调
{
if (!this.MapEvent.has(id)) return null;
var item=this.MapEvent.get(id);
return item;
}
SendClickEvent(id, data)
{
var event=this.GetEventCallback(id);
if (event && event.Callback)
{
event.Callback(event,data,this);
}
}
UIOnMouseMove(e)
{
var pixelTatio = GetDevicePixelRatio();
var x = (e.clientX-this.UIElement.getBoundingClientRect().left)*pixelTatio;
var y = (e.clientY-this.UIElement.getBoundingClientRect().top)*pixelTatio;
var oldMouseOnStatus=this.LastMouseStatus.MouseOnStatus;
this.LastMouseStatus.OnMouseMove=null;
var bDrawTooltip=false;
if (this.LastMouseStatus.TooltipStatus) bDrawTooltip=true;
this.LastMouseStatus.TooltipStatus=null;
var chartTooltipData=null;
this.LastMouseStatus.OnMouseMove={ X:x, Y:y };
var mouseStatus={ Cursor:"default", Name:"Default"};; //鼠标状态
var chart=this.GetCalendarChart();
var bDraw=false;
var cell=null;
if (chart)
{
var data=chart.OnMouseOn(x, y, e); //单元格提示信息
if (data)
{
if (data.Redraw===true) bDraw=data.Redraw;
if (data.Cell)
{
cell=data.Cell;
bDrawTooltip=true;
}
//this.LastMouseStatus.TooltipStatus={ X:x, Y:y, Data:tooltipData, ClientX:e.clientX, ClientY:e.clientY };
}
}
if (bDraw) this.Draw();
//this.SendMouseOnEvent({ Data:cell, X:x, Y:y, FuncName:"UIOnMouseMove" });
if (this.LastMouseStatus.TooltipStatus)
{
var xTooltip = e.clientX-this.UIElement.getBoundingClientRect().left;
var yTooltip = e.clientY-this.UIElement.getBoundingClientRect().top;
//this.DrawFloatTooltip({X:xTooltip, Y:yTooltip, YMove:20/pixelTatio},this.LastMouseStatus.TooltipStatus.Data);
}
else
{
this.HideFloatTooltip();
}
}
UIOnMouseleave(e)
{
this.ClearMoveOnStatus("UIOnMouseleave");
this.HideAllTooltip();
}
UIOnMounseOut(e)
{
this.ClearMoveOnStatus("UIOnMouseleave");
this.HideAllTooltip();
}
ClearMoveOnStatus(funcName)
{
var chart=this.GetCalendarChart();
if (!chart) return;
if (!chart.MoveOnData.Data) return;
chart.MoveOnData.Data=null;
this.Draw();
}
OnWheel(e) //滚轴
{
JSConsole.Chart.Log('[JSCalendarChartContainer::OnWheel]',e);
if (!this.Data) return;
var x = e.clientX-this.UIElement.getBoundingClientRect().left;
var y = e.clientY-this.UIElement.getBoundingClientRect().top;
var isInClient=false;
this.Canvas.beginPath();
this.Canvas.rect(this.Frame.ChartBorder.GetLeft(),this.Frame.ChartBorder.GetTop(),this.Frame.ChartBorder.GetWidth(),this.Frame.ChartBorder.GetHeight());
isInClient=this.Canvas.isPointInPath(x,y);
if (!isInClient) return;
var chart=this.GetCalendarChart();
if (!chart) return;
var wheelValue=e.wheelDelta;
if (!IFrameSplitOperator.IsObjectExist(e.wheelDelta))
wheelValue=e.deltaY* -0.01;
var option={ };
var pageType=0;
var bDraw=false;
if (wheelValue<0) //下
{
this.HideAllTooltip();
if (chart.Type===1)
{
if (this.MoveNextMulitMonth(option)) bDraw=true;
}
else
{
if (this.MoveNextWeek(option)) bDraw=true;
}
}
else if (wheelValue>0) //上
{
this.HideAllTooltip();
if (chart.Type===1)
{
if (this.MovePreviousMulitMonth(option)) bDraw=true;
}
else
{
if (this.MovePreviousWeek(option)) bDraw=true;
}
}
if (bDraw) this.Draw();
if(e.preventDefault) e.preventDefault();
else e.returnValue = false;
}
MoveNextWeek(option)
{
var chart=this.GetCalendarChart();
if (!chart) return;
var range=chart.GetDateLimitRange();
var end=range.End;
var item=this.Data.Days[14];
if (item.Date>=end) return false;
var aryDay=[];
var endDate=null;
for(var i=7; i<this.Data.Days.length;++i)
{
var item=this.Data.Days[i];
aryDay.push(item);
endDate=item.Date;
}
var year=parseInt(endDate/10000);
var month=parseInt(endDate/100)%100;
var day=endDate%100;
var curDate=new Date(year,month-1, day);
for(var i=0; i<7; ++i)
{
curDate.setDate(curDate.getDate() + 1);
var date=curDate.getFullYear()*10000+(curDate.getMonth()+1)*100+curDate.getDate();
var dayItem={ Day: curDate.getDate(), Week:this.GetWeekDayNum(curDate), Date:date, Type:-1 };
dayItem.Text=`${dayItem.Day}`
aryDay.push(dayItem);
}
this.Data.Days=aryDay;
this.CalculateCurrentMonth();
return true;
}
MovePreviousWeek(option)
{
var aryDay=this.Data.Days.slice();
var startDate=aryDay[0].Date;
var year=parseInt(startDate/10000);
var month=parseInt(startDate/100)%100;
var day=startDate%100;
var curDate=new Date(year,month-1, day);
for(var i=0; i<7; ++i)
{
curDate.setDate(curDate.getDate() - 1);
var date=curDate.getFullYear()*10000+(curDate.getMonth()+1)*100+curDate.getDate();
var dayItem={ Day: curDate.getDate(), Week:this.GetWeekDayNum(curDate), Date:date, Type:-1 };
dayItem.Text=`${dayItem.Day}`
aryDay.unshift(dayItem);
}
if (aryDay.length>42) aryDay.length=42;
this.Data.Days=aryDay;
this.CalculateCurrentMonth();
return true;
}
CalculateCurrentMonth()
{
var mapMonth=new Map();
for(var i=0; i<this.Data.Days.length;++i)
{
var item=this.Data.Days[i];
var value=parseInt(item.Date/100);
if (mapMonth.has(value))
{
mapMonth.get(value).Count++;
}
else
{
mapMonth.set(value, { Month: value, Count:1 });
}
}
var maxItem=null;
for (var mapItem of mapMonth)
{
// 首次赋值 或 当前值更大则替换
if (!maxItem || mapItem[1].Count > maxItem.Count)
maxItem=mapItem[1];
}
for(var i=0; i<this.Data.Days.length;++i)
{
var item=this.Data.Days[i];
var value=parseInt(item.Date/100);
if (value>maxItem.Month) item.Type=1;
else if (value<maxItem.Month) item.Type=-1;
else item.Type=0;
}
this.Data.Current.Year=parseInt(maxItem.Month/100);
this.Data.Current.Month=maxItem.Month%100;
}
GetCalendarChart()
{
return this.ChartPaint[0];
}
Draw()
{
if (this.UIElement.width<=0 || this.UIElement.height<=0) return;
this.Canvas.clearRect(0,0,this.UIElement.width,this.UIElement.height);
var pixelTatio = GetDevicePixelRatio(); //获取设备的分辨率
this.Canvas.lineWidth=pixelTatio; //手机端需要根据分辨率比调整线段宽度
this.Frame.Draw();
this.Frame.DrawLogo();
//框架内图形
for(var i=0;i<this.ChartPaint.length;++i)
{
var item=this.ChartPaint[i];
if (item.IsDrawFirst)
item.Draw();
}
for(var i=0; i<this.ChartPaint.length; ++i)
{
var item=this.ChartPaint[i];
if (!item.IsDrawFirst)
item.Draw();
}
}
OnSize()
{
if (!this.Frame) return;
this.SetSizeChange(true);
this.Draw();
}
SetSizeChange(bChanged)
{
var chart=this.GetCalendarChart();
if (chart) chart.SizeChange=bChanged;
}
GetWeekDayNum(date)
{
const d = date.getDay();
return d === 0 ? 7 : d;
};
GetDays(year, month)
{
// 当月第一天
const firstDate = new Date(year, month - 1, 1);
// 当月最后一天
const lastDate = new Date(year, month, 0);
const dayCount = lastDate.getDate();
// getDay:0周日 1周一 ... 6周六
// 转换规则:周一=0,周日=6
let firstDayIndex = firstDate.getDay();
firstDayIndex = firstDayIndex === 0 ? 6 : firstDayIndex - 1;
// ========== 1、前置填充上个月日期 ==========
const aryPreDay=[];
// 上月最后一天
const prevLastDate = new Date(year, month - 1, 0);
for (var i = firstDayIndex; i >= 0; i--)
{
var curDate = new Date(prevLastDate);
curDate.setDate(prevLastDate.getDate() - i);
var date=curDate.getFullYear()*10000+(curDate.getMonth()+1)*100+curDate.getDate();
aryPreDay.push({ Day: curDate.getDate(), Week:this.GetWeekDayNum(curDate), Date:date, Type:-1 });
}
// ========== 2、填充当月全部日期 ==========
const aryDays = [];
for (var d = 1; d <= dayCount; d++)
{
var curDate = new Date(year, month - 1, d);
var date=curDate.getFullYear()*10000+(curDate.getMonth()+1)*100+curDate.getDate();
aryDays.push({ Day: curDate.getDate(), Week:this.GetWeekDayNum(curDate), Date:date, Type:0 });
}
// 合并前置 + 当月
var allDays = [...aryPreDay, ...aryDays];
// ========== 3、尾部填充下个月日期,凑满7天整行 ==========
var fillCount = 6*7-allDays.length;
if (fillCount>0)
{
// 下月第一天
let nextDate = new Date(year, month, 1);
for (var i = 1; i <= fillCount; i++)
{
var curDate = new Date(nextDate);
curDate.setDate(nextDate.getDate() + i - 1);
var date=curDate.getFullYear()*10000+(curDate.getMonth()+1)*100+curDate.getDate();
allDays.push({ Day: curDate.getDate(), Week:this.GetWeekDayNum(curDate), Date:date, Type:1 });
}
}
return allDays;
}
GetMonths(year, month)
{
const curDate = new Date(year, month - 1, 1);
var aryMonth=[];
for(var i=0;i<16;++i)
{
var month=curDate.getMonth()+1;
var date=curDate.getFullYear()*10000+(curDate.getMonth()+1)*100;
aryMonth.push({ Month:curDate.getMonth()+1, Date:date })
curDate.setMonth(curDate.getMonth()+1);
}
return aryMonth;
}
GotoToday(option)
{
var chart=this.GetCalendarChart();
if (!chart) return;
var today=new Date();
var year=today.getFullYear();
var month=today.getMonth()+1;
if (chart.Type===1) this.GotoMulitMonth(year, 1, option);
else this.GotoMonth(year, month, option);
}
GotoMonth(year, month, option)
{
var aryDay=this.GetDays(year, month);
this.Data.Days=[];
for(var i=0;i<aryDay.length;++i)
{
var item=aryDay[i];
var dayItem={ Date:item.Date, Type:item.Type, Week:item.Week, Day:item.Day };
dayItem.Text=`${dayItem.Day}`
this.Data.Days.push(dayItem);
}
this.Data.Current.Year=year;
this.Data.Current.Month=month;
if (option)
{
if (option.Draw===true) this.Draw();
}
}
GotoNextMonth(option)
{
var curDate=new Date(this.Data.Current.Year,this.Data.Current.Month-1, 1);
curDate.setMonth(curDate.getMonth()+1);
var year=curDate.getFullYear();
var month=curDate.getMonth()+1;
var chart=this.GetCalendarChart();
if (!chart) return;
var range=chart.GetDateLimitRange();
var end=parseInt(range.End/100);
if (year*100+month>end) return false;
this.GotoMonth(year, month, option);
}
GotoPreviousMonth(option)
{
var curDate=new Date(this.Data.Current.Year,this.Data.Current.Month-1, 1);
curDate.setMonth(curDate.getMonth()-1);
var year=curDate.getFullYear();
var month=curDate.getMonth()+1;
this.GotoMonth(year, month, option);
}
GotoMulitMonth(year, month, option)
{
var aryMonth=this.GetMonths(year, month);
this.Data.Months=[];
for(var i=0;i<aryMonth.length;++i)
{
var item=aryMonth[i];
var dayItem={ Date:item.Date, Month:item.Month };
dayItem.Text=`${dayItem.Month}月`;
this.Data.Months.push(dayItem);
}
var item=this.Data.Months[9];
var curYear=parseInt(item.Date/10000);
for(var i=0;i<this.Data.Months.length;++i)
{
var item=this.Data.Months[i];
var value=parseInt(item.Date/10000);
if (value>curYear) item.Type=1;
else if (value<curYear) item.Type=-1;
else item.Type=0;
}
this.Data.Current.Year=curYear;
this.Data.Current.Month=null;
if (option)
{
if (option.Draw===true) this.Draw();
}
}
GotoNextYear(option)
{
var item=this.Data.Months[0];
var year=parseInt(item.Date/10000);
var month=parseInt(item.Date/100)%100;
var curDate=new Date(year,month-1, 1);
curDate.setFullYear(curDate.getFullYear()+1);
var year=curDate.getFullYear();
var chart=this.GetCalendarChart();
if (!chart) return;
var range=chart.GetDateLimitRange();
var end=parseInt(range.End/10000);
if (year>end) return false;
this.GotoMulitMonth(year,1,option);
}
GotoPreviousYear(option)
{
var item=this.Data.Months[0];
var year=parseInt(item.Date/10000);
var month=parseInt(item.Date/100)%100;
var curDate=new Date(year,month-1, 1);
curDate.setFullYear(curDate.getFullYear()-1);
this.GotoMulitMonth(curDate.getFullYear(),1,option);
}
MoveNextMulitMonth(option)
{
if (!IFrameSplitOperator.IsNonEmptyArray(this.Data.Months)) return false;
var chart=this.GetCalendarChart();
if (!chart) return;
var range=chart.GetDateLimitRange();
var end=range.End;
var item=this.Data.Months[8];
if (item.Date>end) return false;
var item=this.Data.Months[0];
var year=parseInt(item.Date/10000);
var month=parseInt(item.Date/100)%100;
var curDate=new Date(year,month-1, 1);
curDate.setMonth(curDate.getMonth()+4);
this.GotoMulitMonth(curDate.getFullYear(),curDate.getMonth()+1,option);
return true;
}
MovePreviousMulitMonth(option)
{
if (!IFrameSplitOperator.IsNonEmptyArray(this.Data.Months)) return false;
var item=this.Data.Months[0];
var year=parseInt(item.Date/10000);
var month=parseInt(item.Date/100)%100;
var curDate=new Date(year,month-1, 1);
curDate.setMonth(curDate.getMonth()-4);
this.GotoMulitMonth(curDate.getFullYear(),curDate.getMonth()+1,option);
return true;
}
ReloadResource(option)
{
this.Frame.ReloadResource(option);
for(var i=0;i<this.ChartPaint.length;++i)
{
var item=this.ChartPaint[i];
if (item.ReloadResource) item.ReloadResource(option);
}
if (option && (option.Redraw || option.Draw))
{
this.SetSizeChange(true);
this.Draw();
}
}
ClearMarks()
{
this.Data.MapDayMarks=new Map();
}
//{ Date:20250101, Type:0, Color:可选, Radius:可选, YOffset:可选 }
SetMarkData(aryData, option)
{
this.Data.MapDayMarks=new Map();
if (!IFrameSplitOperator.IsNonEmptyArray(aryData)) return;
for(var i=0;i<aryData.length;++i)
{
var item=aryData[i];
if (!IFrameSplitOperator.IsPlusNumber(item.Date)) continue;
this.Data.MapDayMarks.set(item.Date, item);
}
if (option)
{
if (option.Draw===true) this.Draw();
}
}
}
var JSCHART_CALENDAR_BUTTON_ID=
{
MONTH_TITLE_ID:0,
YEAR_TITLE_ID:1,
DAY_CELL_ID:11,
MONTH_CELL_ID:12,
MONTH_UP_ARROW_ID:51,
MONTH_DOWN_ARROW_ID:52,
YEAR_UP_ARROW_ID:53,
YEAR_DOWN_ARROW_ID:54,
};
class JSCalendarFrame
{
ChartBorder;
Canvas; //画布
//BorderColor=g_JSChartResource.DealList.BorderColor; //边框线
LogoTextColor=g_JSChartResource.FrameLogo.TextColor;
LogoTextFont=g_JSChartResource.FrameLogo.Font;
ReloadResource(resource)
{
this.BorderColor=g_JSChartResource.DealList.BorderColor; //边框线
this.LogoTextColor=g_JSChartResource.FrameLogo.TextColor;
this.LogoTextFont=g_JSChartResource.FrameLogo.Font;
}
Draw(option)
{
var left=ToFixedPoint(this.ChartBorder.GetLeft());
var top=ToFixedPoint(this.ChartBorder.GetTop());
var right=ToFixedPoint(this.ChartBorder.GetRight());
var bottom=ToFixedPoint(this.ChartBorder.GetBottom());
var width=right-left;
var height=bottom-top;
}
DrawLogo()
{
var text=g_JSChartResource.FrameLogo.Text;
if (!IFrameSplitOperator.IsString(text)) return;
this.Canvas.fillStyle=this.LogoTextColor;
this.Canvas.font=this.LogoTextFont;
this.Canvas.textAlign = 'left';
this.Canvas.textBaseline = 'bottom';
var x=this.ChartBorder.GetLeft()+5;
var y=this.ChartBorder.GetBottom()-5;
this.Canvas.fillText(text,x,y);
}
}
class ChartCalendar
{
Canvas=null; //画布
ChartBorder=null; //边框信息
ChartFrame=null; //框架画法
Name=null; //名称
ClassName='ChartCalendar'; //类名
IsDrawFirst=false;
GetEventCallback=null;
Data=null; //数据 { Days:[ { Date:, Text: } ], Offset: }
SizeChange=true;
Type=0; //0=日 1=月 2=年
TitleConfig=
{
IsShow:g_JSChartResource.Calendar.Title.IsShow,
HRate:g_JSChartResource.Calendar.Title.HRate,
Color:g_JSChartResource.Calendar.Title.Color,
BGMargin:CloneData(g_JSChartResource.Calendar.Title.BGMargin),
Arrow:CloneData(g_JSChartResource.Calendar.Title.Arrow),
MoveOn:CloneData(g_JSChartResource.Calendar.Title.MoveOn),
Font:CloneData(g_JSChartResource.Calendar.Title.Font),
} // HRate=高度占比
WeekTitleConfig=
{
Color:g_JSChartResource.Calendar.WeekTitle.Color,
HRate:g_JSChartResource.Calendar.WeekTitle.HRate,
Type:g_JSChartResource.Calendar.WeekTitle.Type, //Type 0=1-7 1=1-5
AryText:[ "", "一", "二", "三", "四", "五", "六", "日" ],
Font:CloneData(g_JSChartResource.Calendar.WeekTitle.Font)
};
DayConfig=
{
Text:CloneData(g_JSChartResource.Calendar.Day.Text),
MoveOn:CloneData(g_JSChartResource.Calendar.Day.MoveOn),
Today:CloneData(g_JSChartResource.Calendar.Day.Today),
Font:CloneData(g_JSChartResource.Calendar.Day.Font),
Selected:CloneData(g_JSChartResource.Calendar.Day.Selected),
}
MarkDayConfig=
{
Dots:CloneData(g_JSChartResource.Calendar.MarkDay.Dots),
}
MonthConfig=
{
Text:CloneData(g_JSChartResource.Calendar.Month.Text),
MoveOn:CloneData(g_JSChartResource.Calendar.Month.MoveOn),
Today:CloneData(g_JSChartResource.Calendar.Month.Today),
Font:CloneData(g_JSChartResource.Calendar.Month.Font),
}
ArySelectedDay=[ ]; //选中的日期 [{ Date:20260618 } ]
//缓存
RectClient=null;
MoveOnData={ Data:null, Enable:true }; //鼠标在上面
AryCellRect=[];
SetOption(option)
{
if (!option) return;
if (option.Title)
{
var item=option.Title;
if (IFrameSplitOperator.IsBool(item.IsShow)) this.TitleConfig.IsShow=item.IsShow;
if (IFrameSplitOperator.IsNumber(item.Type)) this.Type=item.Type;
}
}
ReloadResource(resource)
{
this.TitleConfig.HRate=g_JSChartResource.Calendar.Title.HRate;
this.TitleConfig.Color=g_JSChartResource.Calendar.Title.Color;
this.TitleConfig.BGMargin=CloneData(g_JSChartResource.Calendar.Title.BGMargin);
this.TitleConfig.Arrow=CloneData(g_JSChartResource.Calendar.Title.Arrow);
this.TitleConfig.MoveOn=CloneData(g_JSChartResource.Calendar.Title.MoveOn);
this.TitleConfig.Font=CloneData(g_JSChartResource.Calendar.Title.Font);
this.WeekTitleConfig.Color=g_JSChartResource.Calendar.WeekTitle.Color;
this.WeekTitleConfig.HRate=g_JSChartResource.Calendar.WeekTitle.HRate;
this.WeekTitleConfig.Type=g_JSChartResource.Calendar.WeekTitle.Type;
this.WeekTitleConfig.Font=CloneData(g_JSChartResource.Calendar.WeekTitle.Font);
this.DayConfig.Text=CloneData(g_JSChartResource.Calendar.Day.Text);
this.DayConfig.MoveOn=CloneData(g_JSChartResource.Calendar.Day.MoveOn);
this.DayConfig.Today=CloneData(g_JSChartResource.Calendar.Day.Today);
this.DayConfig.Font=CloneData(g_JSChartResource.Calendar.Day.Font);
this.DayConfig.Selected=CloneData(g_JSChartResource.Calendar.Day.Selected);
this.MarkDayConfig.Dots=CloneData(g_JSChartResource.Calendar.MarkDay.Dots),
this.MonthConfig.Text=CloneData(g_JSChartResource.Calendar.Month.Text);
this.MonthConfig.MoveOn=CloneData(g_JSChartResource.Calendar.Month.MoveOn);
this.MonthConfig.Today=CloneData(g_JSChartResource.Calendar.Month.Today);
this.MonthConfig.Font=CloneData(g_JSChartResource.Calendar.Month.Font);
}
ChangeType(type)
{
this.Type=type;
this.SizeChange=true;
}
Draw()
{
this.AryCellRect=[];
if (this.SizeChange) this.CalculateSize();
if (this.Type===1)
{
this.DrawTitle();
this.DrawMonths();
}
else if (this.Type===2)
{
this.DrawTitle();
this.DrawYears();
}
else
{
this.DrawTitle();
this.DrawWeekTitle();
this.DrawDays();
}
this.SizeChange=false;
}
GetWeekTitleInfo()
{
if (this.WeekTitleConfig.Type===1) return { Count:5, AryWeek:[ 1,2,3,4,5 ] }
else return { Count:7, AryWeek:[1,2,3,4,5,6,7]};
}
CalculateDayModeSize()
{
var border=this.ChartBorder.GetBorder();
var weekTitle=this.GetWeekTitleInfo();
var colCount=weekTitle.Count;
this.RectClient={ Left:border.Left, Top:border.Top, Bottom:border.Bottom, Right:border.Right };
this.RectClient.Width=this.RectClient.Right-this.RectClient.Left;
this.RectClient.Height=this.RectClient.Bottom-this.RectClient.Top;
var weekHeight=this.RectClient.Height*this.WeekTitleConfig.HRate;
var titleHeight=0;
if (this.TitleConfig.IsShow) titleHeight=this.RectClient.Height*this.TitleConfig.HRate;
this.RectClient.WeekHeight=weekHeight;
this.RectClient.TitleHeight=titleHeight;
var dateHeight=this.RectClient.Height-weekHeight-titleHeight;
this.RectClient.Cell={ Width:this.RectClient.Width/colCount, Height:dateHeight/6 };
}
CalculateMonthModeSize()
{
var border=this.ChartBorder.GetBorder();
var colCount=4;
this.RectClient={ Left:border.Left, Top:border.Top, Bottom:border.Bottom, Right:border.Right };
this.RectClient.Width=this.RectClient.Right-this.RectClient.Left;
this.RectClient.Height=this.RectClient.Bottom-this.RectClient.Top;
var titleHeight=0;
if (this.TitleConfig.IsShow) titleHeight=this.RectClient.Height*this.TitleConfig.HRate;
this.RectClient.TitleHeight=titleHeight;
var dateHeight=this.RectClient.Height-titleHeight;
this.RectClient.Cell={ Width:this.RectClient.Width/colCount, Height:dateHeight/4 };
}
CalculateSize() //计算大小
{
if (this.Type===1) this.CalculateMonthModeSize();
else this.CalculateDayModeSize();
}
DrawTitleMoveOnBG(rtBG, config)
{
if (!config || !config.BGColor) return;
if (config.Type===1)
{
this.Canvas.fillStyle=config.BGColor;
this.Canvas.fillRect(rtBG.Left,rtBG.Top,rtBG.Width,rtBG.Height);
}
else
{
var radius=[3];
if (config.RoundRect && IFrameSplitOperator.IsNonEmptyArray(config.RoundRect.Radius)) radius=config.RoundRect.Radius;
var path=new Path2D();
path.roundRect(ToFixedPoint(rtBG.Left), ToFixedPoint(rtBG.Top), ToFixedRect(rtBG.Width), ToFixedRect(rtBG.Height), radius);
this.Canvas.fillStyle=config.BGColor;
this.Canvas.fill(path);
}
}
DrawTitle()
{
var config=this.TitleConfig;
if (!config.IsShow) return;
if (!this.Data || !this.Data.Current) return;
if (this.Type==1) var text=`${this.Data.Current.Year}年`;
else var text=`${this.Data.Current.Year}年${this.Data.Current.Month}月`;
var top=this.RectClient.Top;
var left=this.RectClient.Left;
var cellSize=this.RectClient.Cell;
const fontSize = this.RectClient.TitleHeight*config.Font.SizeRate;
var font=`${fontSize}px ${config.Font.Family}`;
this.Canvas.font=font;
this.Canvas.textAlign="left";
this.Canvas.textBaseline="bottom";
var margin=config.BGMargin;
var rtCell={ Left:left+margin.Left, Top:top+margin.Top, Width:this.RectClient.Width-2*cellSize.Width-(margin.Left+margin.Right), Height:this.RectClient.TitleHeight-(margin.Top+margin.Bottom) };
rtCell.Right=rtCell.Left+rtCell.Width;
rtCell.Bottom=rtCell.Top+rtCell.Height;
var btnTitleID=JSCHART_CALENDAR_BUTTON_ID.MONTH_TITLE_ID;
if (this.Type===1) btnTitleID=JSCHART_CALENDAR_BUTTON_ID.YEAR_TITLE_ID;
this.AryCellRect.push({ Type:btnTitleID, Rect:rtCell, Data:this.Data.Current });
var bMoveOnTitle=false;
if (this.MoveOnData && this.MoveOnData.Data)
{
var item=this.MoveOnData.Data;
if (item.Type===btnTitleID)
{
this.DrawTitleMoveOnBG(rtCell, config.MoveOn);
bMoveOnTitle=true;
}
}
var xText=left+cellSize.Width*0.3;
var yText=this.RectClient.Top+this.RectClient.TitleHeight/2+fontSize/2;
var color=config.Color;
if (bMoveOnTitle && config.MoveOn.Color) color=config.MoveOn.Color;
this.Canvas.fillStyle=color;
this.Canvas.fillText(text, xText, yText);
// 上下箭头
var rtDown={ Top:rtCell.Top, Bottom:rtCell.Bottom, Width:rtCell.Height, Height:rtCell.Height };
rtDown.Right=this.RectClient.Right-cellSize.Width*0.3;
rtDown.Left=rtDown.Right-rtDown.Width;
var btnDownID=JSCHART_CALENDAR_BUTTON_ID.MONTH_DOWN_ARROW_ID;
if (this.Type===1) btnDownID=JSCHART_CALENDAR_BUTTON_ID.YEAR_DOWN_ARROW_ID;
this.AryCellRect.push({ Type:btnDownID, Rect:rtDown, Data:null });
var bMoveOnArrow=false;
if (this.MoveOnData && this.MoveOnData.Data)
{
var item=this.MoveOnData.Data;
if (item.Type===btnDownID)
{
this.DrawTitleMoveOnBG(rtDown, config.MoveOn);
bMoveOnArrow=true;
}
}
this.DrawTitleArrow(rtDown, 1);
var rtUp={ Top:rtCell.Top, Bottom:rtCell.Bottom, Width:rtCell.Height, Height:rtCell.Height };
rtUp.Right=rtDown.Left-cellSize.Width*0.1;
rtUp.Left=rtUp.Right-rtUp.Width;
var btnUpID=JSCHART_CALENDAR_BUTTON_ID.MONTH_UP_ARROW_ID;
if (this.Type===1) btnUpID=JSCHART_CALENDAR_BUTTON_ID.YEAR_UP_ARROW_ID;
this.AryCellRect.push({ Type:btnUpID, Rect:rtUp, Data:null });
var bMoveOnArrow=false;
if (this.MoveOnData && this.MoveOnData.Data)
{
var item=this.MoveOnData.Data;
if (item.Type===btnUpID)
{
this.DrawTitleMoveOnBG(rtUp, config.MoveOn);
bMoveOnArrow=true;
}
}
this.DrawTitleArrow(rtUp, 0);
}
DrawTitleArrow(rtCell, type)
{
var config=this.TitleConfig;
const scale = config.Arrow.Scale; // 缩放比值,1=最大
const triSide = rtCell.Width * scale;
const triH = Math.sqrt(3) / 2 * triSide;
// 区域内居中偏移
const offsetX = (rtCell.Width - triSide) / 2;
const offsetY = (rtCell.Height - triH) / 2;
// 三角形起始坐标(加上rtCell左上角)
const x0 = rtCell.Left + offsetX;
const y0 = rtCell.Top + offsetY;
// 绘制等边三角形
if (type===0)
{
this.Canvas.beginPath();
this.Canvas.moveTo(x0 + triSide / 2, y0); // 顶部顶点
this.Canvas.lineTo(x0, y0 + triH); // 左下角
this.Canvas.lineTo(x0 + triSide, y0 + triH); // 右下角
this.Canvas.closePath();
}
else
{
this.Canvas.beginPath();
this.Canvas.moveTo(x0, y0);
this.Canvas.lineTo(x0 + triSide, y0);
this.Canvas.lineTo(x0 + triSide / 2, y0 + triH);
this.Canvas.closePath();
}
this.Canvas.fillStyle = config.Arrow.Color;
this.Canvas.fill();
}
DrawWeekTitle()
{
var config=this.WeekTitleConfig;
var weekTitle=this.GetWeekTitleInfo();
var colCount=weekTitle.Count;
var yTop=this.RectClient.Top+this.RectClient.TitleHeight;
var left=this.RectClient.Left;
var cellSize=this.RectClient.Cell;
const fontSize = this.RectClient.WeekHeight*config.Font.SizeRate;
var font=`${fontSize}px ${config.Font.Family}`;
this.Canvas.font=font;
this.Canvas.textAlign="left";
this.Canvas.textBaseline="bottom";
var xCell=left, yCell=yTop;
for(var i=0; i<colCount; ++i)
{
var value=weekTitle.AryWeek[i];
var text=config.AryText[value];
var rtCell={ Left:xCell, Top:yCell, Width:cellSize.Width, Height:this.RectClient.WeekHeight };
rtCell.Right=rtCell.Left+rtCell.Width;
rtCell.Bottom=rtCell.Top+rtCell.Height;
if ((i%2)===0)
{
//this.Canvas.fillStyle="rgb(250,100,0)";
//this.Canvas.fillRect(rtCell.Left,rtCell.Top,rtCell.Width,rtCell.Height);
}
if (text)
{
var textWidth=this.Canvas.measureText(text).width;
var xText=rtCell.Left+(rtCell.Width-textWidth)/2;
var yText=rtCell.Top+rtCell.Height/2+fontSize/2;
this.Canvas.fillStyle=config.Color;
this.Canvas.fillText(text, xText, yText);
}
xCell=rtCell.Right;
}
}
DrawMonths()
{
if (!this.Data || !IFrameSplitOperator.IsNonEmptyArray(this.Data.Months)) return;
var colCount=4;
var config=this.MonthConfig;
var cellSize=this.RectClient.Cell;
const dayFontSize = Math.min(cellSize.Width, cellSize.Height)*config.Font.SizeRate;
var dayFont=`${dayFontSize}px ${config.Font.Family}`;
var date=new Date();
var today=date.getFullYear()*10000+(date.getMonth()+1)*100;
var range=this.GetDateLimitRange();
this.Canvas.font=dayFont;
this.Canvas.textAlign="left";
this.Canvas.textBaseline="bottom";
var left=this.RectClient.Left;
var xCell=left, yCell=this.RectClient.Top+this.RectClient.TitleHeight;
var xText, yText;
for(var i=0, j=0; i<this.Data.Months.length;++i)
{
var dayItem=this.Data.Months[i];
var rtCell={ Left:xCell, Top:yCell, Width:cellSize.Width, Height:cellSize.Height };
rtCell.Right=rtCell.Left+rtCell.Width;
rtCell.Bottom=rtCell.Top+rtCell.Height;
var bDisable=(dayItem.Date>range.End || dayItem.Date<range.Start);
var bMoveOn=false;
var bToday=false;
if (today===dayItem.Date)
{
this.Canvas.fillStyle=config.Today.BGColor;
var radius=Math.min(cellSize.Width, cellSize.Height)*0.40;
var x=rtCell.Left+rtCell.Width/2, y=rtCell.Top+rtCell.Height/2;
this.Canvas.beginPath();
this.Canvas.arc(x, y, radius, 0, Math.PI * 2);
this.Canvas.fill();
bToday=true;
}
else if (this.MoveOnData && this.MoveOnData.Data)
{
var item=this.MoveOnData.Data;
if (item.Type===JSCHART_CALENDAR_BUTTON_ID.MONTH_CELL_ID && item.Data.Date==dayItem.Date)
{
if (config.MoveOn.BGColor)
{
this.Canvas.fillStyle=config.MoveOn.BGColor;
var radius=Math.min(cellSize.Width, cellSize.Height)*0.42;
var x=rtCell.Left+rtCell.Width/2, y=rtCell.Top+rtCell.Height/2;
this.Canvas.beginPath();
this.Canvas.arc(x, y, radius, 0, Math.PI * 2);
this.Canvas.fill();
//this.Canvas.fillRect(rtCell.Left,rtCell.Top,rtCell.Width,rtCell.Height);
}
bMoveOn=true;
}
}
if (!bDisable) this.AryCellRect.push({ Type:JSCHAR