当前位置: 首页 > article >正文

自己编写甘特图的绘制程序

甘特图的绘制的数学原理

假设我有一个任务列表数组:Days = [(任务1, day_start, day_end), (任务2, day_start, day_end), ..., (任务n,day_start, day_end)]

在这个列表中:

  • 一定会有一个任务的开始时间在所有其他任务开始之前(或刚好相等),设该任务为:(任务i, day_start_i, day_end_i);

  • 一定会有一个任务的结束时间在所有其他任务结束之后(或刚好相同),设该任务为:(任务j,day_start_j, day_end_j);

我们需要有一个坐标轴来展示这些任务,问题是这些坐标轴的范围如何确认呢?假设我需要用以下的方式展示这个甘特图:

我们以固定甘特图的绘制的大小的方式来绘制甘特图,至于视图层要以什么样的viewport观察这个甘特图,那是视图层QGraphicsView的任务,我不想管这个,因为别人已经实现地很好了。

由于不重叠Item的Scene在视图层渲染方面的性能是足够的,这一点在官方所给的40000flips例子中已经得到验证,所以不需要担心缩放范围太大或太小,或者任务的数量太多。因为以目前人类的脑力水平来说,管理上百个任务已经是比较大的项目了,如果任务上千甚至上万,程序也能够表现良好。因此可以认为,该甘特图可以符合在大多数情况下的项目管理的甘特图编写的需求。

甘特图的总宽度

这里假设day_start_i = (year_start_i, month_start_i, day_start_i),则最左边的月份轴为:year_start_i年month_start_i月的上一个月(由于月份的计算需要进行回绕,所以数学方面不好表述出来)。

假设day_end_j = (year_end_j, month_end_j, day_end_j),则最右边的月份轴为:year_end_j年month_end_j月的下一个月。

因此,需要绘制的坐标轴的范围是:[最左月份轴,最右月份轴],假设这个序列为:[axis_0, axis_1, ..., axis_m]。

假设左上坐标为(0, 0),那么最左月份轴的x轴坐标为多少呢?不可能定义为0,因为这样的话月份轴可能渲染不到。那么究竟定义为多少呢?这里我从月份轴上的xxxx年xx月​入手,假设这个文本框的显示需要的大小为[w_month, h_month]​,如果想要这个文本框刚好位于最左上角,然后对应的月份轴居中于该文本框,则月份轴的x坐标为:w_month / 2​。

最左月份轴的x轴坐标定义完成,那么最左的下一个月份轴的x坐标是什么?下下个呢?由于我们期望这些月份轴之间间隔相同,因此我们需要定义好月份之间的间隔,假设这个间隔的距离为month_spacing​。那么最左月份的下一个月份轴的x轴坐标是x = w_month / 2 + month_spacing * i​,i是月份轴在序列中的下标。

那么,整个甘特图的最右边的x坐标(注意不是最右边月份轴的)是:w_month + month_spacing * m​。

这个图很清晰地反映了月份轴的x轴的坐标分布。只要我们定义好:

  • 月份文本框的大小(w_month, h_month)​。

  • 月份轴间的间隔month_spacing​。

  • 月份轴的数量m + 1​。

那么就可以计算得出整个甘特图的总宽度w_month + month_spacing * m​。

甘特图的总高度

我们前面假设我们具有n个任务,任务的y轴坐标应该如何定义?

时间轴t我们可以简单定义为:(x, y = h_month)​。

答案是:h_month + top + (h + spacing) * i​,这里的h_month​是月份文本框的高度,而top​则是第一个任务与时间轴t的距离(毕竟总不能贴在一起),至于h​则是甘特图中任务的范围矩形的高度,spacing​则是任务之间的间隔距离。以上这些都是程序获得任务列表之前能够定义好的,但只有获得了任务列表之后,我们才能获得i​,即任务在任务数组中的下标。

就像我们不想要第一个任务紧贴时间轴t,我们也不想要最后一个任务紧贴甘特图的底边,最后一个任务距离甘特图的底边有bottom​的距离。

最终,我们可以得出甘特图的总高度为:h_month + top + (h + spacing) * (n-1) + bottom​。

因此,我们可以确定,在预先定义好了种种常量(像h_monthtop​之类的)之后,在用户输入任务列表时,我们可以马上确定甘特图的大小是多少。因此,我们可以完全可以将甘特图放入一个Item中。

月份轴的文本框坐标计算

月份轴按从小到大排序,并分配下标,下标为i的月份轴对应的文本框的坐标为:(month_spacing * i, 0)​。

任务i的坐标计算

假设任务i在任务数组中的下标为i,我们需要计算得出其左上坐标,和右下坐标,方便绘制图形。

左上坐标为:(x_i_start, h_month + top + (h + spacing) * i)​,右下坐标为:(x_i_end, h_month + top + (h + spacing) * i + h)​。

这里的重点在于求出x_i_start​和x_i_end​。

对于x_i_start​我们可以使用x_{i-start}=\frac{任务i的开始日}{任务i所在月份的天数}\times month\_spacing+任务i所在月份的月份轴的x轴坐标。

对于x_i_end​的计算其实与x_i_start​同理。

因此,我们只要有计算得出任务i所在月份的天数的方法,由于任务i的开始日已知,我们就可以很轻松地计算出这些坐标。

而某年某月的天数,可以使用Python中的calender​库来获取,只需指定年月就可以通过获取其天数。


到此为止,关于甘特图大部分内容的数学原理都已经基本完成了,接下来就是设计好代码结构,合理定义接口,把这些数学原理实现出来。

就算以后要使用以天为单位的甘特图,那我们也可以新搞一个可以显示更加详细数据的甘特图。这个月份的甘特图就可以作为概要图来显示。

软件接口定义

考虑到数据量不会太过复杂,所以这里采用所有任务都需要进行统一的运算,也就是说,我会将所有任务都打包送给某个对象进行处理,处理完成之后,外界可以从该对象获取处理完成的信息。如果原来的任务有任何一个发生变化,那么就需要对所有任务进行的坐标信息之类的进行修改,因此,任务越多,计算任务就越繁重。这是一种不好的设计,我认为不应该这样做。

关于甘特图的总高度和总宽度,我们可以通过动态统计它们,从而不需要重新计算所有任务。

对于新添加的任务,无论这个任务的范围是什么,只要我们调整了总宽度和总高度以及月份轴的位置,那么原有的任务的框的计算方式不需要变化,我们只需要为新添加的任务分配下标和坐标,其他任务的下标改变无所谓,因为不影响显示。

即新添加的任务完全可以只分配好坐标和下标即可,而无需担心原有的绘制和新的绘制出现冲突,因为间隔的距离保证了它们之间不会有冲突。但是绘制的代价不会变,每一次修改我们必然需要重新绘制一遍甘特图,这个绘制的开销与任务的数量是成正相关的,我们无法改变(只要发生了修改),但是我们可以通过缓存避免不必要的刷新,但根据官方的示例,只要不是大量重叠,大数量的Item的刷新根本不是问题。因此,这个方法很大概率是可行的。

因此,关于甘特图绘制的数学原理应该是这样一个对象:可以定义初始任务列表,并在后续动态向里面添加任务的对象,这个对象维护着月份轴的位置,总高度,总宽度,月份文本框大小,各种间隔数据供Item对象取用的对象。这是一个为甘特图Item提供坐标计算辅助的对象!

对象的数据组成

  • 月份轴数组:由于月份轴数据涉及到年份,所以这里打算使用“年月”(可对Python中的datetime​作一定的限制来使用,或者利用该对象封装一个合适的MonthTime)作为键,而值为月份轴的x轴坐标。仅存储所需要的月份轴坐标,也就是说如果任务的增减导致了有些月份轴没有必要存在,则删除对应的月份轴。

  • 间隔设置:提供一些基本的间隔数据以定义好甘特图的格式,如甘特图总高度或总宽度,月份框大小,任务垂直间隔,上下间隔等等。

计算任务的左上和右下坐标时,需要通过获得月份轴坐标来实时计算,这是因为任务的位置是相对于月份轴而言的。

因此,维护月份轴数据很重要:

  • 当添加新任务或删除现有任务时,需要更新月份轴数据,还有甘特图的总高度和总宽度数据。

经过试验的合适常量:

  • 年月文本框的大小:QSizeF(74.1875, 24.0)​。

  • month_spacing​,由于年月文本框的宽度为74.1875​,如果month_spacing < 74.1875​则两个相邻的文本框将会重叠,因此month_spacing >= 74.1875​,我想要这两个文本框相隔远一点,因此定义month_spacing = 180.0​。

  • bottom = top = spacing = 12.0​。

  • h = 24.0​作为任务框的高度。

现在的问题是,如何定义输入的任务列表呢?这个任务列表最好还要兼容未来的数据库。这里我的解决方案是,数据库方面的搞一个查询,查询任务和开始时间和结束时间,然后经过程序的处理变成对象可以处理的东西。因此,在这里,我们需要定义好这样一个对象。

简单搭建一番之后,可以做到这样程度的排布:

接下来是使用GanttExplainer来绘制完整的甘特图。我们就先用矩形框代替将来可能的任务条Item先,后续再加上一个文本之类的。

在经过粗糙的搭建后,达成了以下的效果:

可以看到,我们的程序已经可以粗略地将这个甘特图的数据显示出来了,虽然很简陋但至少能用不是吗?就先这样子。


http://www.kler.cn/a/460358.html

相关文章:

  • xdoj校验和
  • 虚拟机中的时统卡功能和性能调优
  • 【数据库系列】Spring Boot 中整合 MyBatis-Plus详细步骤
  • 聊聊 C# 中的委托
  • 《计算机组成及汇编语言原理》阅读笔记:p177-p177
  • Scrapy和Selenium结合使用完整步骤
  • golang 熔断限流降级
  • 商汤C++开发面试题及参考答案
  • 【postgresql 物化视图】自动刷新物化视图2种方法
  • order by语句执行顺序
  • Vue2/Vue3使用DataV
  • .net core 的数据库编程
  • swiftui开发页面加载发送请求初始化@State变量
  • flash-attention保姆级安装教程
  • 脚本方式 迁移 老GITLAB项目到新GITLAB
  • 前端vue+el-input实现输入框中文字高亮标红效果(学习自掘金博主文章)
  • 服务器系统维护与安全配置
  • 黑马商城:MybatisPlus
  • img上的title属性和alt属性的区别是什么?
  • Oracle 数据库 dmp文件从高版本导入低版本的问题处理
  • C++ 环境搭建 - 安装编译器、IDE选择
  • WebRTC音视频通话系统需求(项目预算)
  • ffmpeg 编译+ libx264
  • Golang 的AI 框架库
  • Windows电脑带有日历的桌面备忘记事工具
  • shell脚本的使用