Добавление Графиков В Notion

Многим людям не хватает диаграмм в Notion. Поэтому я решил создать автоматическую вещь для их генерации.

Вот как это выглядит с моей стороны:

Добавление графиков в Notion

Кому интересно, как это реализовано, обращайтесь в кат.



Часть 1. Постановка задачи

Проблема, по сути, в том, что в Notion нет графиков, а информацию из таблицы нельзя просто так визуализировать (а хотелось бы).

Соответственно, вам нужно создать что-то, что:

  1. Возьмем список страниц, где потенциально могут быть диаграммы.

  2. Соберу описание расписания с этих страниц.

    Описания должны быть на страницах для наглядности читателю страницы + чтобы не залезать слишком часто в код для исправления.

  3. Добавит график сразу после его описания, удалив предыдущую версию графика.

  4. Сделает это автоматически (раз в час) и, желательно, бесплатно.



Часть 2. Раскладываем по полочкам

Мы напишем сервер на Django. А именно Django, потому что неофициальная библиотека Notion API написана на Python. Затем мы загружаем все это в Heroku. Из IFTTT мы будем с определенной частотой вытягивать свои вещи на Heroku.

Добавление графиков в Notion



Часть 3. Разбираемся, что нам нужно написать

  1. Метод ответа на запрос от IFTTT
  2. Метод прочесывания страниц Notion и поиска описаний диаграмм.

  3. Метод получения данных для графика из таблицы
  4. Метод рисования графика и добавления его на страницу


Часть 4. Написание кода

Перейдите в Notion, нажмите Ctrl + Shift + J, перейдите в Application -> Cookies, скопируйте token_v2 и назовите его TOKEN. Это необходимо для того, чтобы библиотека могла как-то взаимодействовать с Notion API. Нам нужно как-то хранить список страниц, где потенциально могут встречаться описания для графиков.

Мы сохраним этот вопрос просто:

  
  
  
  
  
  
  
  
  
  
  
  
   

PAGES = [ " https://www.notion.so/mixedself/Dashboard-40a3156030fd4d9cb1935993e1f2c7eb " ]

Для того, чтобы как-то разобрать само описание, нам нужны ключевые слова для:
  1. Поля, данные которых будут находиться на нашей оси X
  2. Поля, данные которых будут находиться на нашей оси Y
  3. URL-адрес вывески
Это будет выглядеть так:

BASE_KEY = "Base:" X_AXIS_KEY = "X axis:" Y_AXIS_KEY = "Y axis:"

То есть наше пустое описание графика будет выглядеть примерно так:

def get_empty_object(): return { "database": "", "x": "", "y": "" }

Еще нам нужно как-то проверить, пусто ли описание.

Для этого напишем специальную функцию.

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



def is_not_empty(thing): return thing != "" def check_for_completeness(object): return is_not_empty(object["database"]) and is_not_empty(object["x"]) and is_not_empty(object["y"])

Данные (по сути это просто текст) для формирования описания надо как-то очистить.

Напишем для этого пару функций.

Небольшое уточнение: Notion хранит жирный шрифт (как на картинке выше) __вот так__.

def br_text(text): return "__" + text + "__" def clear_text(text): return text.replace(br_text(BASE_KEY), "").

replace(BASE_KEY, "") \ .

replace(br_text(X_AXIS_KEY), "").

replace(X_AXIS_KEY, "") \ .

replace(br_text(Y_AXIS_KEY), "").

replace(Y_AXIS_KEY, "").

strip()

Теперь напишем, пожалуй, основную функцию для нашей штучки.

Ниже кода приведено объяснение того, что здесь происходит:

def plot(): client = NotionClient(token_v2=TOKEN) for page in PAGES: blocks = client.get_block(page) thing = get_empty_object() for i in range(len(blocks.children)): block = blocks.children[i] print(block.type) if block.type != "image": title = block.title if BASE_KEY in title: thing["database"] = clear_text(title).

split("](")[0].

replace("[", "") elif X_AXIS_KEY in title: thing["x"] = clear_text(title) elif Y_AXIS_KEY in title: thing["y"] = clear_text(title) if check_for_completeness(thing): # not last block if i != len(blocks.children) - 1: next_block = blocks.children[i + 1] # if next block is picture, then it is previous # version of the plot, then we should remove it if blocks.children[i + 1].

type == "image": next_block.remove() draw_plot(client, thing, block, blocks) thing = get_empty_object()

Подключаем нашу библиотеку к Notion. Затем мы просматриваем массив страниц, где нам потенциально могут понадобиться графики.

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

Далее мы напишем функцию для сбора данных из таблицы.



def get_lines_array(thing, client): database = client.get_collection_view(thing["database"]) rows = database.default_query().

execute() lines_array = [] for i in range(1, len(rows)): previous_row = rows[i - 1] current_row = rows[i] line = [(get_point_from_row(thing, previous_row)), (get_point_from_row(thing, current_row))] lines_array.append(line) return lines_array

Здесь мы минируем базу, получаем все ее линии и проходим по всем линиям, образуя набор линий от точки к точке.

Что такое get_point_from_row? Дело в том, что если наш объект — дата (часто необходимо отобразить дату по оси X), то отобразить его просто невозможно, и нам необходимо его дополнительно обработать:

def get_point_from_row(thing, row): x_property = row.get_property(thing["x"]) y_property = row.get_property(thing["y"]) if thing["x"] == "date": x_property = x_property.start if thing["y"] == "date": y_property = y_property.start return x_property, y_property

Теперь мы готовы нарисовать наш график.



def draw_plot(client, thing, block, page): photo = page.children.add_new(ImageBlock) photo.move_to(block, "after") array = get_lines_array(thing, client) print(array) for i in range(1, len(array)): points = reparse_points(array[i - 1:i][0]) plt.plot(points[0], points[1], color="red") if not path.exists("images"): os.mkdir("images") if thing["x"] == "date": x_axis_dates() filename = "images/" + random_string(15) + ".

png" plt.savefig(filename) print("Uploading " + filename) photo.upload_file(filename)

Здесь добавляем новый блок (с фотографией) и перемещаем его под описание графика.

Затем мы повторно анализируем точки (подробнее об этом ниже), рисуем линии с помощью matplotlib, сохраняем полученное изображение со случайным именем файла и загружаем его в блок изображения.

Мы можем получить случайное имя файла следующим образом:

def random_string(string_length=10): letters = string.ascii_lowercase return ''.

join(random.choice(letters) for i in range(string_length))

И нам нужно повторно проанализировать эти точки, потому что matplotlib принимает в качестве входных данных представление данных, отличное от того, как оно реализовано в настоящее время.



def reparse_points(points): return [ [points[0][0], points[1][0]], [points[0][1], points[1][1]], ]

Если присмотреться, метод также проверяет, являются ли данные, которые у нас есть на оси X, датой.

Если да, то нам просто нужно отобразить его правильно:

def x_axis_dates(ax=None, fig=None): if ax is None: ax = plt.gca() if fig is None: fig = plt.gcf() loc = mdates.AutoDateLocator() fmt = mdates.AutoDateFormatter(loc) ax.xaxis.set_major_locator(loc) ax.xaxis.set_major_formatter(fmt) fig.autofmt_xdate()

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

Почему ПОСТ? На всякий случай, чтобы если на вас вдруг кто-то посмотрит, скрипт не запустился.

Зачем новая тема? IFTTT, который мы будем использовать в качестве триггера для работы этой штуки, не любит очень долго ждать ответа от сервера (а в нашем случае ожидание может быть долгим), и через какое-то время может перестать запускаться вещь.



@csrf_exempt def index(request): if request.method == "POST": thread = Thread(target=plot) thread.start() return HttpResponse("Hello, world.") else: return HttpResponse("Hello, world.")



Часть 5. IFTTT

Перейдите на вкладку создания апплета.

Выберите триггер (в нашем случае это «Дата и время») и установите для него значение «каждый час».

Выбираем сработавший (то есть «тот») Webhook, указываем наш (пока) локальный адрес, чтобы его протестировать.

Вот и все.

Мы тестируем.



Загрузка в Хероку

Вы подумали, зачем мы заморачивались с этим триггером со стороны IFTTT — это чтобы не платить.

Heroku предлагает бесплатный хостинг для нашей вещицы.

Главное, чтобы сервис спал не менее 6 часов.

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

Далее делаем следующее.

Пойдем в героку создать новый проект .

Далее установите его в свою операционную систему.

их клиент .

А дальше делаем все по инструкции, которая появится после создания приложения.

Загрузив все на героку, заходим в наш апплет и редактируем URL на новый.



Часть 5. IFTTT

Спасибо всем, кто дочитал до этого места.

Надеюсь, эта статья вам в чем-то помогла.

Вы можете прочитать две другие мои статьи о Notion: Автоматически экспортируйте формы Google в Notion с помощью IFTTT и Django. Создание домашней библиотеки с помощью Notion и Python Теги: #python #программирование #облачные сервисы #Визуализация данных #ООП #django #graphics #matplotlib #notion #notion

Вместе с данным постом часто просматривают: