Подтвердить что ты не робот

Создание линейного, основанного на временной шкале представления из элементов, которые потребляют время и элементы, которые этого не делают, но все же необходимо пространство для рисования

Это вопрос о создании изображения или любых других представлений для набора параллельных данных. Речь идет не о рисовании или графическом программировании, а о вычислении позиций. Сначала я немного объясню, где я стою прямо сейчас, а второе изображение и пример показывают мою проблему.

Текущее состояние

exampleOne-Easy 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.     

Проблема

Теперь к проблеме. Есть объекты, которые нуждаются в графическом пространстве, но имеют нулевую продолжительность. Я называю это "Смещение".

exampleTwo-Problematic http://www.wargsang.de/text4120.png

Или нам нужны объекты, которые имеют продолжительность, но также имеют смещение. Это все еще не проблема, когда у нас есть только одна строка, но синхронизация строк сложнее, и у меня нет решения для этого.

На рисунке выше красные блоки имеют нулевую длительность и смещаются. Синие блоки имеют длительность и тоже смещены.

Пример: * Представьте себе график проведения конференции с различными слотами динамиков каждый час (наши интервалы продолжительности). Каждая строка представляет собой другой конференц-зал.

  • Черные блоки - это речи и, возможно, содержат короткую тему, написанную внутри них (графически).

  • Синие блоки также являются речами, но тема слишком длинная, чтобы писать, поэтому нам нужно больше места один раз.

  • Красный - это заметки, как изменение номера комнаты. Они не занимают время самостоятельно, но относятся ко всем предметам, которые приходят после них. *

Задача состоит в том, чтобы найти способ калибровки пиксельного счетчика из вышеприведенной функции, чтобы он был правильным для каждой строки, но также и то, что перемещение в одной строке влияет на все остальные строки и создает там дополнительное пространство. Цель состоит в том, что длительности фиксируются и выравниваются в каждой строке. Любое событие или пробел, который должен начинаться, например, счетчик единиц 4, должен начинаться в том же абсолютном положении.

Это означает, что любой объект с нулевой продолжительностью/перемещением начинается в реальном времени по времени/длительности, но не потребляет самого времени и продолжительности, так что все последующие элементы начинаются с той же самой продолжительностью, пока не будет включено следующее событие реальной продолжительности, С другой точки зрения это также означает, что элементы с нулевой длительностью всегда начинаются до событий, имеющих длительность.

На рисунке мы видим довольно простой случай в столбце 2, что также означает, что он запускает второй временной интервал. Несмотря на то, что в этом столбце есть три реальных события, которые сдвинуты вправо, потому что здесь находится элемент смещения. В столбце 4 есть элемент продолжительности, который также имеет смещение. Опять же, все предметы, которые начинаются в слоте 5, сдвигаются вправо. Colum 6 - самая интересная и моя реальная проблема, я не могу найти решение здесь. Опять же, все реальные события в столбце 6 смещены вправо и все еще начинаются в одно и то же время. Но здесь мы имеем 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
            ] 
4b9b3361

Ответ 1

Я не совсем уверен, но я думаю, что вы хотите:

  • смещения в разных строках, которые имеют одинаковое время , синхронизируются, то есть имеют одинаковую горизонтальную позицию (первое смещение по каждой строке синхронизируется с первыми смещениями в других строках)
  • "реальные" события и промежутки, которые имеют одинаковое время запуска, синхронизируются и поступают после перемещений с одинаковым временем запуска
  • ширина события зависит от его продолжительности (и, возможно, от его смещения), но не при смещениях в других строках, т.е. время окончания не синхронизируется

Если вы хотите, чтобы время окончания было синхронизировано, вам нужно будет объяснить, как; но я не вижу очевидного пути.

Затем вы получите следующее решение для своей исходной проблемы (capitals = события, строчные буквы = смещения, точки = пробелы, пробел = "ожидание синхронизации", цифры - время начала событий):

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)]))

Ответ 2

Вам нужна функция для сопоставления времени с координатой 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}). Обработайте все строки один за другим, а затем все они снова для окончательного местоположения.

Это проще, если вы преобразуете свои объекты, чтобы иметь время начала и графический размер. Тогда нет разницы между Gap и Event.

Ответ 3

Если я понимаю ваш вопрос...

Для каждого столбца отслеживайте максимальную ширину ящиков с нулевой длительностью строк, когда вы выполняете начальную обработку каждой строки. Когда вы, наконец, рисуете каждое реальное событие, оно начинается в конце значения максимальной ширины для его столбца. (Я предполагаю, что вы уже вычисляете, где начинается столбец.)

В вашем ярком графе выше, обработка строки 1 даст вам максимальную ширину нулевой продолжительности (например) [0, 10, 0, 0, 0, 10, 0]. Строка 2 не изменит его. Строка 3 изменит ее на [0, 10, 0, 0, 0, 20, 0].

Когда вы рисуете реальное событие, оно будет начинаться со столбца column_start + column max-zero-duration-width.

Ответ 4

Я вижу пару проблем с кодом вашего события: 1) нет способа рассказать, сколько места выделяется для перемещения, независимо от того, является ли событие нулевой длительностью или нет; 2) если у вас есть (скажем, в четвертой строке) событие, которое занимает 3 единицы и начинается с 5, оно будет полностью нарисовано в 5 и 6 слотах, не доведя до 7.

Я подозреваю, что вам нужно что-то вроде:

class Event(object):
    def __init__(self, duration, displacement=0):
        self.duration = duration
        self.displacement = displacement
        self.width = max(STANDARD_WIDTH, duration+displacement)
        #additional data

    def self.draw(self, start_coordinate):
        """draw duration * 10 pixels in black"""
        return self.width * 10

Без обработки графика в виде таблицы я не уверен, как обойти вторую проблему, поднятую выше.

Кроме того, это займет два прохода: первый проход, чтобы выяснить различные ширины столбцов ваших часовых блоков, второй - использовать эту информацию для рисования строк.

Ответ 5

Я рекомендую использовать значок для самого события и строки для продолжительности.