从耗时和不耗时的项目生成线性时间轴表示,但仍需在上面绘制空间
这是一个关于如何为一组平行数据生成图像或其他表示形式的问题。这个问题不是关于绘图或图形用户界面编程,而是关于计算位置。
首先,我会简单介绍一下我目前的情况,接着第二张图片和示例会展示我的问题。
当前状态
示例一-简单 http://www.wargsang.de/text3935.png
我有一些一维的对象,它们通过放置在平行的“线上”来对齐。我们把这些一维对象称为“事件”,它们有一个“持续时间”,作为时间的单位。
这些事件有一种变体,就是没有任何活动的对象,它们没有数据,但有一个持续时间;我们称之为“间隙”对象。
因此,我们得到了一个包含事件和间隙的同时发生的对象的时间表,这样处理起来相对简单,可以看作是三个对象列表。
可视化也很简单:遍历这些列表,根据每个对象的持续时间来绘制它们。
class Event():
def __init__(self, duration, displacement = 0): #displacement is explained in the second example and the core problem of this question
self.duration = duration
self.displacement = displacement
#additional data
def self.draw(self, start_coordinate):
"""draw duration * 10 pixels in black"""
#drawing code using start_coordinate to place the drawn object. see example graphic
return duration * 10
class Gap():
def __init__(self, duration, displacement = 0):
self.duration = duration
self.displacement = displacement
#no data
def self.draw(self, start_coordinate):
"""draw duration * 10 pixels in transparent"""
#drawing code using start_coordinate to place the drawn object. see example graphic
return duration * 10
row_one = [Event(1), Gap(1), Event(1), Gap(1), Event(1), Gap(1), Event(2)]
row_two = [Event(1), Gap(2), Event(1), Event(1), Gap(1), Event(1), Gap(1), ]
row_thr = [Gap(1), Event(1), Gap(1), Event(1), Gap(1), Event(3),]
timetable = [row_one, row_two, row_thr]
for row in timetable:
pixelcounter = 0 # the current position.
for item in row:
ret = item.draw(pixelcounter) #draw on the current position. Get how width the item was
pixelcounter += ret #save width for the next iteration
#instructions to move the "drawing cursor" down a few pixels so the next row does not overlap.
问题
现在说说问题。存在一些需要图形空间但持续时间为零的对象。我称之为“位移”。
示例二-有问题 http://www.wargsang.de/text4120.png
或者我们需要一些有持续时间但也有位移的对象。当我们只有一行时,这仍然不是问题,但同步多行就复杂多了,我还没有找到解决方案。
在上面的图片中,红色块的持续时间为零并且有位移。蓝色块也有持续时间,并且同样有位移。
示例: *想象一下一个会议的时间表,每小时有不同的演讲者(我们的持续时间槽)。每一行代表一个不同的会议室。
黑色块是演讲,可能在里面写有简短的主题(图形上)。
蓝色块也是演讲,但主题太长,无法写下,所以我们需要更多的空间。
红色块是像房间号变更这样的备注。它们本身不占用时间,但与所有在它们之后的项目相关联。
任务是找到一种方法,从上面的函数计算像素计数,使得每一行都是正确的,同时一行的位移影响所有其他行,并在那儿创建额外的空间。
目标是每一行的持续时间是固定且对齐的。任何应该在,例如,单位计数4上开始的事件或间隙,应该在同一个绝对位置开始。
这意味着任何零持续时间/位移的对象在时间/持续时间上开始于一个真实的点,但本身不消耗任何时间/持续时间,因此所有后续项目在下一个真实的持续时间事件之前都在同一个持续时间上开始。从另一个角度来看,这也意味着零持续时间的项目总是在有持续时间的事件之前开始。
在图片中,我们可以看到第二列是一个相对简单的情况,这也意味着它开始了第二个持续时间槽。尽管在那一列中有三个真实事件,但由于有一个位移项目,它们都向右移动。
第四列有一个有持续时间的项目,也有位移。同样,所有在第五槽开始的项目都向右移动。
第六列是最有趣的,也是我真正的问题,我在这里找不到解决方案。再次强调,第六列中的所有真实事件都向右移动,但仍然在同一时间开始。但这里有a)两个行中的位移对象和b)两个对象紧挨着。
因此,真实事件需要知道完整的位移,但第三行中的第二个对象也需要知道在它之前还有一个位移项目。
警告:图形表示可能会暗示一种基于表格的方法,其中每一列都有单独的宽度。但这就是这个例子的结束。实际应用处理的事件持续时间通常在300到10,000之间,但持续时间为1的情况不太可能,但在技术上是可能的。因此,表格的列宽将是一个持续时间。考虑到我们进入了数十万的完整持续时间(乘以行数),这可能会拖慢性能。
这张图片的数据看起来是这样的。我该如何用这些数据绘制第二张图片?或者需要改变什么,我对所有建议持开放态度。
非常感谢你的时间和关注。如果你不知道解决方案,请随时问我问题或指出我概念中的缺陷,这将帮助我思考。
row_one = [ Event(1), #1
Event(0,1), Event(1), #2
Gap(1), #3
Event(1), #4
Gap(1), #5
Event(0,1), Event(1), #6
Event(1), #7
]
row_two = [ Event(1), #1
Event(1), #2
Gap(1), #3
Event(1, 0.5), #4, 0,5 is not important. we can also simply to just ints. The important bit is that it has both values.
Event(1), #5
Event(1), #6
Event(1), #7
]
row_thr = [ Event(1), #1
Event(1), #2
Event(1), #3
Event(1), #4
Event(1), #5
Event(0,1), Event(0,1), Event(1), #6 #Please pay attention to this case.
Event(1), #7
]
5 个回答
如果我理解你的问题没错的话……
对于每一列,在处理每一行的时候,要记录下那些持续时间为零的框的最大宽度。这样,当你最后要画出真正的事件时,它就会从该列的最大宽度的末尾开始。(我假设你已经在计算这一列的起始位置了。)
在你上面的彩色图表中,处理第一行时,你会得到持续时间为零的框的最大宽度,比如说是 [0, 10, 0, 0, 0, 10, 0]。第二行不会改变这个结果。第三行会把它改成 [0, 10, 0, 0, 0, 20, 0]。
当你要画一个真正的事件时,它的起始位置会是这一列的起始位置加上该列的最大零持续时间宽度。
你需要一个函数来把时间映射到x坐标上。比较难的地方是,有时候你在某个时刻需要更多的空间。这个问题可以通过一个对象列表来解决,这个列表记录了什么时候需要额外的空间,以及需要多少。
function timeToLocation(t)
location = t * scale
for (o : extraSpaceList)
if o.when < t
location = location + o.space
return location
每当你尝试放置一个对象,发现空间不够(因为元素重叠了),你只需在需要的时刻插入一些额外的空间,比如用 extraSpaceList.add({when=2s,space=4pixels})
。然后逐行处理所有的对象,最后再处理一遍,找到它们的最终位置。
如果你把对象转换成有开始时间和图形大小的形式,这样就更简单了。这样“间隙”和“事件”就没有区别了。
我不太确定,但我觉得你想要的是:
- 在不同的行上,开始时间相同的位移是同步的,也就是说,它们的水平位置是一样的(每行的第一个位移和其他行的第一个位移是同步的)
- “真实”的事件和间隙,如果它们的开始时间相同也是同步的,并且是在相同开始时间的位移之后出现
- 事件的宽度取决于它的持续时间(可能还包括它的位移),但不依赖于其他行的位移,也就是说,结束时间是不同步的
如果你想让结束时间也同步,那你需要解释一下怎么做;不过我没有看到明显的办法。
然后你会得到以下解决方案来处理你最初的问题(大写字母 = 事件,小写字母 = 位移,点 = 间隙,空格 = “等待同步”,数字是事件的开始时间):
0 123 4 567
AbC.D .e FG
A B.CcD EF
A BCD EfgHI
在下面的例子中,你可以看到结束时间并没有同步:
0 12
AAa
Aa B
AaaB
还有一个更大的随机例子:
11 11 1 1 1 1 1 1 22 22 2 2 22 22 33 333 3 3 3 3 3 4 44444 4 4 4 45
01 2 34 5678 9 01 23 4 5 6 7 8 9 01 23 4 5 67 89 01 234 5 6 7 8 9 0 12345 6 7 8 90
AAAA BB CCC dd.. EEe Fff.. GGGGGg .... ... HHH .... IIii JJJ ... KKK LLLLl
abbbCCC DDDDDdd .. EEEEE Fff GGG HHH IIIii JJJjj KKKK LLLl Mno. PPP qR SSSSSs TT uuuVV
... AAAAA BBB CC DDDD ... EE FFFF GHhhIIII JJ. K Lll.m.... NNNO ....
...... AAAA .. .... BBB CCCCCc DDDDDd Ee FFFFff G hhhIIIII JJJ KLLLLLll M
.. AAA BBBCcc DD EE .. FFF gH IIIIIi J KKk LL MMMMM NNNNNn OOo PPQQQQ rrr...
AAAAa . BBBBbb CCCCC DDDDDd eeeFFFFF GG HH ..... IIIII JJ K LM.NNNNN .
AAAaaBBB CCCcc DDDDDdd EeFF ... GGgHHHH III JJJJ KKK llMMMm nnnOOOO PPPp ... Q
AAAAA BBBBB CCCC ..... DDD EEEEE FFFff .... GGGG HHHHhh II.... j . .
AAAaa.. BBBBbb CccDDDDD .... EEE .F GgghhhII Jj KKKK ... ... LLll ... MMMM N OooP
.... Aa ..BCCC ..... DDD EEEe FFf ..... GGGG HIIIIIii . JJ .... KKk LL
AAAAAa bbC..... DDDDD .... eeFFFFff GGGGG ... hh IIJJJ KKK L MMMMMmmNNNN
..aBBB CCCCc ..... ..... ... D. E FFFFFff ggHHhiiiJKKKk LLLLL mmmNNNOP Q RRR
AA BbCCCC DD Ee FFFFFff GGGGG HH IIIi JjjK.. LLLll MMMMmm .... . NNNOOOOOoo P
AB CCCCC ..... ddEEEE fffGgg HHHHHhh II jjKKKK LLLL MMMM nn.. OO PPPPPpp QQQQQqq
AAA BBB CCCC DDdd EE FFF gggHh IIIii JJJJ K LLLLl MMm NNOOOO . PP .QQQRRRRR
现在是代码(抱歉这么长,看看Timetable.__init__
部分,那里是有趣的,其余的主要是格式化输出)。
from heapq import merge
from itertools import groupby, cycle, chain
from collections import defaultdict
from operator import attrgetter
from string import ascii_uppercase
# events are processed in this order:
# increasing start time, displacements (duration=0) first, and grouped by row_id
ev_sort_attrs = attrgetter("time", "duration", "row_id")
class Event:
def __init__(self, duration, displacement=0, visible=True, time=None, row_id=None):
self.duration = duration
self.displacement = displacement
self.visible = visible
self.time = time
self.row_id = row_id
self.pos = None
def draw(self, char):
return char * self.duration + char.lower() * self.displacement
def __lt__(self, other):
return ev_sort_attrs(self) < ev_sort_attrs(other)
def Gap(duration):
return Event(duration, visible=False)
class Timetable(list):
def __init__(self, *args):
""" compute positions for a list of rows of events """
list.__init__(self, *args)
# compute times for the events, and give them row_ids
for i, row in enumerate(self):
t = 0
for ev in row:
ev.time = t
t += ev.duration
ev.row_id = i
# map times to position for displacements and event
t2pos_disp = defaultdict(int) # maps times to position of synchronized start of displacements
t2pos_ev = defaultdict(int) # maps times to position of synchronized start of events and gaps
# the real work is done in the following loop
t_prev = 0
for t, g in groupby(merge(*self), key=attrgetter("time")):
# different times should have a minimum distance corresponding to their difference
t2pos_ev[t] = t2pos_disp[t] = max(t2pos_ev[t], t2pos_ev[t_prev] + t - t_prev)
t_prev = t
for (duration, row_id), g_row in groupby(g, key=attrgetter("duration", "row_id")): # process all displacements first, then the events
pos_ev = t2pos_ev[t] if duration > 0 else t2pos_disp[t] # events and displacements start at different
for ev in g_row:
ev.pos = pos_ev
pos_ev += ev.duration + ev.displacement
t2pos_ev[t + ev.duration] = max(t2pos_ev[t + ev.duration], pos_ev)
# keep our results...
self.t2pos_ev = t2pos_ev
self.t2pos_disp = t2pos_disp
@staticmethod
def str_row(row):
""" draw row, uppercase letters for real events, lower case letters for
displacements, dots for gaps"""
ev_chars = cycle(ascii_uppercase)
out = []
l = 0
for ev in row:
if ev.pos > l:
out.append(" " * (ev.pos - l))
out.append(ev.draw(next(ev_chars) if ev.visible else "."))
l = ev.pos + len(out[-1])
return "".join(out)
def __str__(self):
max_t, max_p = max(self.t2pos_ev.items())
w = len(str(max_t))
header_temp = [" " * w] * (max_p + 1)
for t, p in self.t2pos_ev.items():
header_temp[p] = "%*d" % (w, t)
headers = ("".join(header) for header in zip(*header_temp))
rows = (self.str_row(row) for row in self)
return "\n".join(chain(headers, rows))
if __name__ == "__main__":
# original example
row_one = [Event(1), Event(0,1), Event(1), Gap(1), Event(1), Gap(1), Event(0,1), Event(1), Event(1)]
row_two = [Event(1), Event(1), Gap(1), Event(1, 1), Event(1), Event(1), Event(1)]
row_thr = [Event(1), Event(1), Event(1), Event(1), Event(1), Event(0,1), Event(0,1), Event(1), Event(1)]
timetable = Timetable([row_one, row_two, row_thr])
print(timetable)
print("-" * 80)
# short example, shows ending times are not synchronized
print(Timetable([[Event(2, 1)], [Event(1, 1), Event(1)], [Event(1, 2), Event(1)]]))
print("-" * 80)
# larger random example
def random_row(l):
import random
res = []
t = 0
while t < l:
x = random.random()
if x < 0.1: res.append(Event(0, random.randint(1, 3)))
elif x < 0.8: res.append(Event(min(random.randint(1, 5), l - t), random.randint(0, 1) * random.randint(0, 2)))
else: res.append(Gap(min(random.randint(1, 5), l - t)))
t += res[-1].duration
return res
print(Timetable([random_row(50) for _ in range(15)]))