Создание и визуализация пользовательских диаграмм и графиков в QGIS при помощи R
по адресу http://gis-lab.info/qa/qgis-custom-plots-r.html
В QGIS есть встроенные инструменты построения графиков. С их помощью вы можете построить гистограммы, круговые и текстовые диаграммы. К сожалению, встроенные инструменты обладают рядом недостатков: множественные артефакты при рендеринге, громоздкость при отображении больших объёмов данных, непривлекательный вид. Частично эти проблемы можно решить путём создания собственных графиков и диаграмм. В данной статье показано, как, используя программную среду R, можно создавать пользовательские диаграммы и графики для QGIS.
Идея
Мы можем создать необходимые графики в формате SVG сторонними инструментами и отображать их в качестве [части] условного знака необходимого объекта. Для создания диаграмм мы будем использовать программную среду R, в частности, пакет "ggplot2".
Смоделируем ситуацию. Предположим, нам надо отобразить на диаграмме некоторые величины, находящиеся в атрибутах векторного слоя. Пусть у нас будет 12 атрибутов с необходимыми значениями, но мы знаем, что в будущем их количество увеличится до 20.
Если мы попытаемся использовать встроенные инструменты, то полученные диаграммы загромоздят значительную область экрана (гистограммы), либо будут практически нечитаемы (текстовые диаграммы). Нам же больше всего подойдут радиальные диаграммы.
Вот так будет выглядеть конечный результат (два варианта):
Итак, нам нужно решить следующие задачи:
- Создать диаграммы в формате SVG.
- Вывести их на карту в качестве элемента условного знака объекта.
Так как вторая задача проще и, возможно, вы предпочитаете использовать другое ПО для создания диаграмм, то сначала объясним, как визуализировать уже готовые диаграммы.
Визуализация диаграмм в QGIS
Здесь всё довольно просто. Поместите все диаграммы в одну папку и дайте им имена, соответствующие уникальным идентификаторам объектов в таблице атрибутов к которым они относятся. Например, если у векторного слоя есть атрибут "ID", содержащий значения типа 0, 1, 2..., то назовите соответствующие им диаграммы 0.svg, 1.svg, 2.svg...
Теперь перейдём к редактированию векторного слоя. В таблице атрибутов создайте текстовое поле, в котором будет храниться путь к диаграммам (убедитесь, что поле будет способно вместить достаточное количество символов). Для заполнения атрибутов воспользуемся калькулятором полей. Вот это простое выражение позволит справиться с задачей:
'/путь/к/папке/с/диаграммами/' || "ID" || '.svg'
Приведённое выражение создаёт строку текста путём присоединения к пути к папке значения атрибута "ID" и расширения файла. Таким образом, для каждого объекта слоя будет записан путь к файлу с его диаграммой. Обратите внимание, что статические (неизменные) значения заключены в одинарные кавычки ('), а динамические (извлекаемые из соответствующего атрибута слоя) - в двойные ("). Кроме того, не забывайте, что последним символом в пути к папке должен быть слеш, в противном случае, полученный путь будет неправильным (например, 'путь0.svg' вместо 'путь/0.svg'). Создание атрибута для сохранения пути и его заполнение можно произвести одновременно — выберите опцию "создать новое поле" в калькуляторе полей.
Для того, чтобы отобразить диаграммы в качестве условного знака, надо зайти в свойства точечного слоя и на вкладке Style задать тип символа слоя — SVG-маркер (если у вас полигональный слой, то Centroid Fill -> Marker -> SVG-marker). Справа от адресной строки задания пути к файлу нажмите кнопку "Data defined override" и выберите Field type: sting и задайте нужный атрибут, см. скриншот:
Обязательно задайте размеры маркера побольше — от 15 мм, иначе диаграмма будет нечитаема.
Создание скрипта на R для построения диаграмм
Это — самая сложная часть задачи. Неискушённому пользователю придётся порядочно покопаться в документации к пакету ggplot2 и соответствующих примерах, чтобы разобраться, как контролировать различные аспекты визуализации. Не будем вдаваться во все тонкости (читайте мануалы), отметим только основные моменты.
Вот какие задачи необходимо решить на этапе создания диаграмм:
- Подобрать палитру
Для этого как нельзя лучше подходит инструмент для подбора палитры Color Brewer. К сожалению, обеспечить отчётливое восприятие отдельных цветов и оттенков возможно только при довольно ограниченном наборе объектов для раскрашивания. Естественное ограничение — примерно 10 цветов, нам же нужно составить палитру для 20(!). Но выход есть — можно объединить две "качественные" палитры по 10 цветов:
- Добавить несуществующие данные
Это необходимо для того, чтобы внешний облик диаграмм не претерпевал значительных изменений при добавлении новых значений (см. код скрипта ниже).
- Сделать фон диаграммы прозрачным
(Здесь и далее приведены параметры для построения графиков в пакете "ggplot2")
plot.background = element_blank()
- Сделать фон области значений диаграммы прозрачным
panel.background = element_blank()
- Удалить сетку области значений диаграммы (опционально)
panel.grid = element_blank()
- Удалить легенду
legend.position = 'none'
- Удалить подписи к осям и объектам
axis.text.x = element_blank(), axis.text.y = element_blank()
- Обрезать поля диаграммы
plot.margin = unit(c(0,0,-0.3,-0.3), 'cm')
- Сохранить диаграммы на диск с сохранением прозрачности фона
ggsave(file = 'имя_файла_и_путь', plot = p, width = 5, height = 5, units = 'cm', bg = "transparent")
- Создать полноценную диаграмму-легенду для расшифровки созданных диаграмм
Для этого нужно построить диаграмму такого же типа, как и для объектов слоя, но с легендой, подписями и максимальными значениями данных.
Скрипт на R для Processing Toolbox в QGIS
Удобно было бы иметь прямо в QGIS инструмент, который позволил бы генерировать необходимые диаграммы. Приведём пример скрипта для запуска из QGIS Processing Tiilbox. Можете запустить окно создания R-скрипта в Processing Toolbox, вставить туда код, приведённый ниже, и сохранить (необходимо предварительно установить R и подключить его в QGIS). Скрипт появится в разделе R-scripts -> Plotting.
Вот так выглядит окно настраиваемых параметров скрипта при его запуске из Processing Toolbox:
Список параметров:
- layer — Векторный слой с данными для диаграммы.
- ID — Атрибут, содержащий уникальные значения.
- start_field — Атрибут, с которого начинается серия атрибутов, содержащих значения для построения диаграмм (атрибуты должны идти подряд).
- end_field — Атрибут, на котором заканчивается серия атрибутов, содержащих значения для построения диаграмм (атрибуты должны идти подряд).
- add_ghost_data — Требуется ли добавлять несуществующие атрибуты? Ставьте галочку, если планируете в будущем добавить дополнительные атрибуты и переделывать диаграммы.
- total_bins_including_ghosts — Если требуется добавить несуществующие атрибуты, указать общее количество атрибутов по которым будут строиться диаграммы в будущем.
- ghost_value — Если требуется добавить несуществующие значения, укажите значение, которое будет присвоено дополнительным атрибутам на данный момент.
- plot_width — Ширина SVG-диаграмм в сантиметрах.
- plot_height — Высота SVG-диаграмм в сантиметрах.
- palette — Палитра для раскраски значений атрибутов диаграммы в виде HEX-кодов (наличие решётки "#" необязательно), разделённых запятой. Если количество элементов палитры будет меньше количества атрибутов, то часть атрибутов не будет отображена на диаграмме. Количество элементов палитры может превышать количество атрибутов.
- out_folder — Папка в которую будут записаны диаграммы. Несмотря на приписку "[optional]", этот параметр является обязательным.
- R plots — Путь, по которому будет сохранена диаграмма-легенда
После окончания работы скрипта в папке, указанной в out_folder, появятся SVG-диаграммы, а пользователю будет показана диаграмма-легенда:
Ниже приведён код скрипта для запуска посредством Processing Toolbox, снабжённый необходимыми комментариями. Обратите внимание, что элементы графического интерфейса (входные параметры скрипта) задаются двойным знаком "#":
##Переменная = параметр дефолтное_значение
##Plotting = group
##layer=vector
##ID=field layer
##start_field= field layer
##end_field= field layer
##add_ghost_data=boolean False
##total_bins_including_ghosts=number 20
##ghost_value=number 0
##plot_width=number 5
##plot_height=number 5
##palette=string #a6cee3,#1f78b4,#b2df8a,#33a02c,#fb9a99,#e31a1c,#fdbf6f,#ff7f00,#cab2d6,#6a3d9a,#8dd3c7,#ffffb3,#bebada,#fb8072,#80b1d3,#fdb462,#b3de69,#fccde5,#d9d9d9,#bc80bd
##out_folder=folder
##showplots
library(ggplot2)
library(reshape2)
library(grid)
# R не допускает пробелов в именах атрибутов, а также начала имени атрибута с цифры.
# Необходимо привести загруженные в R имена атрибутов в соответсвие с их именами в векторном слое
fixFieldName <- function(field_name){
field_name <- gsub(' ', '.', field_name)
if( is.na(as.integer(substr(field_name,1,1))) == F ) {
field_name <- paste('X', field_name, sep = '')
}
field_name
}
palette <- strsplit(palette, ',')
palette_1 <- c()
# Processing удаляет знак '#' из строки параметров — надо это исправить
for (i in palette) {
char = substr(i,1,1)
print(char)
if ( char != '#') {
colour <- paste('#', i, sep = '')
palette_1 <- c(palette_1, colour)
}
}
palette <- palette_1
# Processing не добавляет слеш в конец адреса папки. Надо это исправить.
slash <- substr(out_folder, nchar(out_folder), nchar(out_folder))
if (slash != '/' | slash != '\\' ) {
out_folder <- paste(out_folder, '/', sep = '')
}
ID <- fixFieldName(ID)
start_field <- fixFieldName(start_field)
end_field <- fixFieldName(end_field)
dFrame <- as.data.frame(layer)
ID <- match(ID, names(dFrame))
start_field <- match(start_field, names(dFrame))
end_field <- match(end_field, names(dFrame))
print(start_field)
print(end_field)
max_value <- max(dFrame[,start_field:end_field])
addGhostData <- function(temp_df, d_value = 0, n_bins = 20){
# Эта функция добавляет несуществующие данные
temp_df$variable <- as.character(temp_df$variable) # необходимо для добавления новых строк
# создаём необходимое количество записей
if (total_columns < n_bins) {
for (col in (total_columns + 1):n_bins) {
row <- c(paste('X', 'ZZ99', col, sep = ''), d_value) # так в 90% случаев добавленные записи будут в конце легенды на диаграмме ;-)
temp_df <- rbind(temp_df, row)
}
}
# возвращаем датафрейм в нормальное состояние
temp_df$variable <- as.factor(temp_df$variable)
temp_df$value <- as.integer(temp_df$value)
temp_df
}
# создаём и записываем диаграммы
for (i in c( 1:nrow(dFrame) ) ) {
# подготовка данных
total_columns <- end_field - start_field +1
temp_df <- dFrame[,start_field:end_field][i,]
temp_df <- melt(temp_df)
# добавляем несуществующие атрибуты, если требуется
if (add_ghost_data) {
temp_df <- addGhostData(temp_df, ghost_value, total_bins_including_ghosts)
}
# рисуем диаграмму
p <- ggplot(temp_df, aes(x = variable, y = value)) +
geom_bar(aes(fill = variable), colour = 'white', stat="identity")+
coord_polar()+
ylim(0, max_value)+ # set y limits so all charts will be comparable
theme(axis.text.x = element_blank(),
axis.text.y = element_blank(),
axis.title = element_blank(),
axis.ticks = element_blank(),
title = element_text(face = 'bold', size = 16),
legend.position = 'none',
legend.title = element_blank(),
legend.text = element_text(size = 10),
plot.margin = unit(c(0,0,-0.3,-0.3), 'cm'), # удаляем поля!!!
plot.background = element_blank(), # прозрачный фон диаграммы
panel.background = element_blank(), # прозрачный фон области значений диаграммы
panel.grid = element_blank() # удаляем сетку области диаграммы (закомментируйте эту строку и запятую на предыдущей строке, чтобы оставить сетку)
)+
labs(x=NULL, y=NULL, title = NULL)+ # ещё раз удалим все подписи ;-)
scale_fill_manual(values = palette) # раскрасим диаграмму в нужные цвета
# получаем имя файла
fid <- dFrame[,ID][i]
file_name <- paste(out_folder, fid, '.svg', sep = '')
# сохраняем диаграмму
ggsave(file = file_name, plot = p, width = plot_width, height = plot_height, units = 'cm', bg = "transparent")
}
# Создаём диаграмму-легенду
temp_df <- dFrame[,start_field:end_field]
l <- list(NULL)
for ( i in (1:ncol(temp_df)) ){l[[i]] = max_value}
temp_df <- rbind(temp_df, l)
row <- nrow(temp_df)
new_df <- temp_df[row,]
new_df <- melt(new_df)
# добавляем несуществующие атрибуты, если надо
if (add_ghost_data) {
new_df <- addGhostData(new_df, ghost_value, total_bins_including_ghosts)
}
# удаляем нежелательные знаки из имён атрибутов, а в 5% случаев — нужные (но ничего не поделать!)
new_df$variable <- as.character(new_df$variable)
for (i in (1:nrow(new_df))) {
variable <- new_df[,1][i]
print(variable)
new_df[,1][i] <- substr(variable, 2, nchar(variable))
}
new_df$variable <- as.factor(new_df$variable)
# Строим диаграмму-легенду
p <- ggplot(new_df, aes(x = variable, y = value)) +
geom_bar(aes(fill = variable), colour = 'white', stat="identity")+
coord_polar()+
ylim(0, max_value)+ # set y limits so all charts will be comparable
labs(title = 'Reference chart',
legend = 'Attribute') +
theme(axis.text.x = element_text(angle=0, hjust = NULL, size = 10, color = 'black'),
axis.text.y = element_text(angle=0, hjust = NULL, size = 10, color = 'black'),
axis.title = element_blank(),#element_text(face = 'bold', size = 16),
title = element_text(face = 'bold', size = 16),
legend.position = 'right',
legend.title = element_blank(),#element_text(angle=0, hjust = NULL, size = 14, color = 'black'),
legend.text = element_text(size = 10)
)+
scale_fill_manual(values = palette)
plot(p)
Заключение
В данной статье было продемонстрировано создание пользовательских диаграмм на основе значений атрибутов векторных данных и их отображение в качестве условных знаков для объектов векторного слоя. Приведённый скрипт для Processing Toolbox в QGIS позволяет генерировать необходимые диаграммы, и может быть использован как основа для создания диаграмм и графиков других типов.