实际上,每个人都无法从Notion表格中可视化数据,这就是为什么我开发了一个可以自动在Notion中绘制图表的工具。
从我的角度来看,它是这样的:
第一部分 问题陈述 #
问题实际上是Notion中没有图表。因此,我们需要创建这样一个工具:
- 将接收一个可能包含图表描述的页面列表。
- 从页面中收集图表的描述。
- 在描述块后面添加一个图表,并删除原来的描述块。 ## 第2部分. 快速解释
我们将使用Django编写服务器,因为非官方的Notion API库是用Python编写的。然后我们将将我们的东西上传到Heroku。我们将使用IFTTT以一定的频率从Heroku拉取我们的东西。
那么我们应该实际上写些什么呢?
- 一个用于响应IFTTT请求的函数
- 一个用于搜索情节描述的函数
- 一个用于绘制情节并将其上传到Notion的函数
- 一个用于从描述中提取数据的函数
第3部分. 编写代码 #
我们去Notion,按下Ctrl + Shift + J,然后我们去应用程序-> Cookie并复制token_v2。让我们将其命名为TOKEN。
我们应该以某种方式存储一个页面数组,其中情节的描述可能存在。实际上很简单:
PAGES = [ "https://www.notion.so/mixedself/Dashboard- 40a3156030fd4d9cb1935993e1f2c7eb" ]
``` 为了某种程度上解析描述本身,我们需要以下关键词:
1. X轴上的数据
2. Y轴上的数据
3. 表格视图的URL
**代码如下所示:**
BASE_KEY = "基础:" X_AXIS_KEY = "X轴:" Y_AXIS_KEY = "Y轴:"
而一个空的图表描述如下所示:
def get_empty_object(): return { "数据库": "", "X轴": "", "Y轴": "" }
我们需要某种方式来检查描述是否为空。为此,我们将编写一个特殊函数:如果所有字段都不为空,则可以开始绘制图表。
def is_not_empty(thing): return thing != ""
def check_for_completeness(object): return is_not_empty(object["数据库"]) 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"] = 我们将我们的库连接到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实际上是什么呢?问题是,如果数据是日期,我们应该重新解析它,以便matplotlib能够正确处理:
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
``` 现在我们准备绘制我们的图表。
```python
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绘制线条,使用随机文件名保存生成的图像,并将其加载到图像块中。
我们需要重新解析这些点,因为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请求时启动一个新线程。为什么是POST?因为如果需要POST请求来开始绘图,那么您不会因为错误(通过访问URL)而启动它。
为什么要新建线程?IFTTT, ## 第4部分. IFTTT
转到创建applets (opens new window)。选择触发器(在我们的情况下是日期和时间),勾选“每小时”。选择Webhook作为触发的事物,在测试目的下设置我们(到目前为止)的本地地址。实际上,就是这样。测试一下吧。
第5部分. Heroku #
你可能会问,为什么我们使用IFTTT作为触发器?因为我们可以使用Heroku,我们的导出程序将免费运行。Heroku有免费的计划来托管我们的事物。最重要的是,服务器每天至少要休眠6个小时。而且他肯定会休眠,因为我们每小时都会触发它,而不是每分钟触发。 然后我们按照以下步骤进行操作:前往Heroku 创建新产品 (opens new window)。然后通过Heroku客户端 (opens new window)安装新的操作系统。然后按照说明进行其他操作。
在将所有内容上传到Heroku后,前往IFTTT的小程序并将旧的URL更改为全新的URL。
现在一切应该正常工作。