Рисование огромных объемов данных в NSView, или что-то еще?


У меня есть сотни тысяч точек данных временных рядов, которые я хочу представить пользователю. Мое текущее решение состоит в том, чтобы отобразить эти данные в PNG с помощью сторонней библиотеки, а затем загрузить этот PNG в NSImage и отобразить его в виде прокрутки. Это прекрасно работает, за исключением того, что:

  1. NSImages шириной более 32k пикселей не отображаются должным образом
  2. я хочу иметь возможность быстро и легко увеличивать масштаб данных.
  3. чтение и запись с диска-это глупый

Моя текущая попытка состоит в том, чтобы непосредственно нарисовать NSBezierPaths к NSView. Вид отображается красиво, но очень, очень медленно, даже если я рисую только ограниченное подмножество точек за один раз. И каждый раз, когда я прокручиваю, мне приходится заново рисовать, что тоже медленно.

Я уверен, как относительный новичок какао, что мне не хватает некоторых лучших способов сделать это. Каков "правильный" способ сделать это?
6 4

6 ответов:

Моя текущая попытка состоит в том, чтобы непосредственно нарисовать NSBezierPaths к NSView. Вид отображается красиво, но очень, очень медленно, даже если я рисую только ограниченное подмножество точек за один раз. И каждый раз, когда я прокручиваю, мне приходится заново рисовать, что тоже медленно.

Прежде чем предпринимать какие-либо радикальные решения, попробуйте выполнить следующие простые шаги:

  1. клип. использование NSRectClip, передача прямоугольника, который вы принимаете в качестве аргумента, в drawRect:. Это один лайнер.
  2. выполните простое тестирование прямоугольника перед заполнением путей. для каждого пути получите его границы и используйте NSIntersectsRect, чтобы проверить, находится ли он внутри прямоугольника.
  3. сделайте чуть менее простое тестирование прямоугольника перед поглаживанием путей. аналогично предыдущему шагу, за исключением того, что вам нужно увеличить прямоугольник (тот, который вы получили в качестве аргумента) на ширину линии, так как половина штриха будет выпадать за пределы пути. Для каждого пути получите ширину линии и отрицайте ее (delta = -[path lineWidth] - и да, вы должны включить этот знак минуса), затем передайте результат в качестве обоих аргументов NSInsetRect. Убедитесь, что вы держите исходный прямоугольник вокруг, так как разные пути могут иметь разную ширину линий.
Идея состоит в том, чтобы просто рисовать меньше. Установка пути отсечения (с помощью NSRectClip) уменьшит количество блитов, возникающих в результате операций рисования. Исключение путей, которые полностью выпадают за пределы прямоугольника рисования, сэкономит вам, вероятно, больше денег. подрезка для этих путей. И, конечно, вы должны делать профиль на каждом шаге, чтобы убедиться, что вы не сделали вещи медленнее. Возможно, вы захотите настроить какой-то счетчик частоты кадров.

Еще одна вещь: получение границ для каждого пути может быть дорогостоящим. (Опять же, профиль.) Если это так, вы можете кэшировать границы для каждого пути, возможно, используя параллель NSArray из NSValues или обернув путь и границы вместе в объект вашего собственного изобретения. Тогда вы вычисляете границы только один раз и просто получить его на будущих тиражах чертежей.

В документах есть раздел о производительности NSBezierPath .

Если вы рисуете так много путей Безье, OpenGL-это, вероятно, путь. Документация Apple была бы хорошим местом для начала. У них естьПример кода для реализации базового контекста рендеринга OpenGL в окне cocoa.

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

На этой странице приведен пример кода OpenGL для рисования кривых Безье.

Я бы посмотрел на CATiledLayer (только для леопарда) для этого. Вы можете нарисовать огромное количество материала в плиточном слое и иметь его отображаемые области по мере необходимости. В вашем случае вы можете установить CATiledLayer в качестве основы для вашего NSView, нарисовать все ваши пути Безье в этом слое,а затем прокручивать и даже увеличивать и уменьшать его. Основные анимационные слои обрабатываются как текстуры OpenGL, поэтому вы должны получить очень хорошую производительность. Векторный рисунок кэшируется в слое и не сохраняется перерисовывайте по мере прокрутки, как вы находите на стандартном NSView.

Например, Билл Дадни опубликовал пример кода о том, как использовать CATiledLayer для отображения массивного PDF-файла.

Насколько динамичен набор данных?

Если он достаточно статичен, и вы хотите "увеличить", то вам следует рассмотреть возможность консолидации данных в масштабируемом подмножестве.

Надуманный пример: если у вас есть 100 000 точек (скажем, продвижение по оси X), то, очевидно, в 1000-пиксельном изображении вы только неизбежно будете строить 1000 точек из этих 100 000.

Таким образом, вы можете сделать это в advanced (1000 pts, 10000 pts, необработанные данные 100000pts) и выбрать из соответствующего набора на основе уровень "масштабирования", который вы показываете.

Что касается того, какое значение выбрать, когда точки перекрываются, вы можете сделать min, max, median, average и т. д.

Рассмотрите возможность использования фреймворка, который эффективно обрабатывает такие вещи, как CorePlot