2024年3月

今天,我们将迅速着手搭建一个高效且富有创意的混元聊天应用,其核心理念可以用一个字来概括——快。在这个快节奏的时代,构建一个基础的LLM(Large Language Model,大型语言模型)聊天应用并不需要耗费太多时间。市面上充斥着各种功能强大的大型语言模型,我们可以根据项目需求灵活选择,而今天的目标并非深入探讨这些模型的技术细节,而是将重点放在如何快速上手。

Streamlit这一强大的工具,它能够让我们以最快速度搭建起一个具备流式打字机效果的聊天应用。对于那些和我一样,对前端代码望而却步的开发者来说,Streamlit无疑是一个福音。

本次实操,我们将不会过多地纠缠于理论知识,而是将重点放在实战操作上。

开始开发

依赖环境

开发之前,请确保你已经配置好了必要的开发环境,以下是你需要准备的一系列环境和工具,以确保开发过程的顺利进行:

Python环境:Python 3.9

腾讯云API服务:从腾讯云控制台开通混元API并且获取腾讯云的SecretID、SecretKey

依赖包安装:

pip install --upgrade tencentcloud-sdk-python

pip install streamlit

如果你对Streamlit还不太熟悉,安装完成后,你可以通过执行
streamlit hello
或者
python -m streamlit hello
启动一下入门实例。如果你希望对Streamlit有更深入的了解,我强烈建议你访问其官方文档。官方文档提供了详尽的指南、教程和API参考。

简易流程

首先,请查阅腾讯云官方简易流程,然后,一旦您成功获取相关信息的申请,填入并检查输出是否正常。

import os
from tencentcloud.common import credential
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
from tencentcloud.cvm.v20170312 import cvm_client, models

try:
    # 为了保护密钥安全,建议将密钥设置在环境变量中或者配置文件中,请参考本文凭证管理章节。
    # 硬编码密钥到代码中有可能随代码泄露而暴露,有安全隐患,并不推荐。
    # cred = credential.Credential("secretId", "secretKey")
    cred = credential.Credential(
        os.environ.get("TENCENTCLOUD_SECRET_ID"),
        os.environ.get("TENCENTCLOUD_SECRET_KEY"))
    client = cvm_client.CvmClient(cred, "ap-shanghai")

    req = models.DescribeInstancesRequest()
    resp = client.DescribeInstances(req)

    print(resp.to_json_string())
except TencentCloudSDKException as err:
    print(err)

如果输出结果呈现是这样的,这便表明所得信息基本正确的,接下来我们便可顺利进行后续的开发工作。

"TotalCount": 0, "InstanceSet": [], "RequestId": "714808e9-684a-4714-96f1-2a9fe77b6e55"

接下来,让我们深入了解Streamlit是如何构建基础的LLM(大型语言模型)聊天应用的,一起查看一下他们的官方演示代码吧。

import streamlit as st
import random
import time

# Streamed response emulator
def response_generator():
    response = random.choice(
        [
            "Hello there! How can I assist you today?",
            "Hi, human! Is there anything I can help you with?",
            "Do you need help?",
        ]
    )
    for word in response.split():
        yield word + " "
        time.sleep(0.05)

st.title("Simple chat")

# Initialize chat history
if "messages" not in st.session_state:
    st.session_state.messages = []

# Display chat messages from history on app rerun
for message in st.session_state.messages:
    with st.chat_message(message["role"]):
        st.markdown(message["content"])

# Accept user input
if prompt := st.chat_input("What is up?"):
    # Add user message to chat history
    st.session_state.messages.append({"role": "user", "content": prompt})
    # Display user message in chat message container
    with st.chat_message("user"):
        st.markdown(prompt)

    # Display assistant response in chat message container
    with st.chat_message("assistant"):
        response = st.write_stream(response_generator())
    # Add assistant response to chat history
    st.session_state.messages.append({"role": "assistant", "content": response})

切记,在运行Streamlit时,不要使用python命令,而应该使用streamlit run [your_script.py],否则可能会持续遇到错误提示。

观察了代码后,可以看出基本框架已经建立好了,接下来的步骤就是替换请求和响应部分。

关于请求和响应的实例,腾讯官方也提供了相关内容。你可以查看以下链接以获取更多信息:

https://github.com/TencentCloud/tencentcloud-sdk-python/blob/master/examples/hunyuan/v20230901/chat_std.py

经过5分钟的修改和代码改进,最终成功地实现了可运行的版本。

还是一样的规矩,最终代码如下:

import json
import os
import streamlit as st
import random
import time

from tencentcloud.common import credential
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
from tencentcloud.common.profile.client_profile import ClientProfile
from tencentcloud.hunyuan.v20230901 import hunyuan_client, models

st.title("混元小助手")
os.environ['id'] = '******'
os.environ['key'] = '******'

# 实例化一个认证对象,入参需要传入腾讯云账户secretId,secretKey
cred = credential.Credential(
    os.environ.get("id"),
    os.environ.get("key"))
cpf = ClientProfile()
# 预先建立连接可以降低访问延迟
cpf.httpProfile.pre_conn_pool_size = 3
client = hunyuan_client.HunyuanClient(cred, "ap-beijing", cpf)
req = models.ChatStdRequest()

# Streamed response emulator
def response_generator():
    # msg = models.Message()
    # msg.Role = "user"
    # msg.Content = content
    req.Messages = []
    for m in st.session_state.messages:
        msg = models.Message()
        msg.Role = m["role"]
        msg.Content = m["content"]
        req.Messages.append(msg)
    
    resp = client.ChatStd(req)

    for event in resp:
        data = json.loads(event['data'])
        for choice in data['Choices']:
            yield choice['Delta']['Content'] + ""
    

# Initialize chat history
if "messages" not in st.session_state:
    st.session_state.messages = []

# Display chat messages from history on app rerun
for message in st.session_state.messages:
    with st.chat_message(message["role"]):
        st.markdown(message["content"])

# Accept user input
if prompt := st.chat_input("有什么需要帮助的?"):
    # Add user message to chat history
    st.session_state.messages.append({"role": "user", "content": prompt})
    # Display user message in chat message container
    with st.chat_message("user"):
        st.markdown(prompt)
    # Display assistant response in chat message container
    with st.chat_message("assistant"):
        response = st.write_stream(response_generator())
    # Add assistant response to chat history
    st.session_state.messages.append({"role": "assistant", "content": response})

在这里需要注意一下,当使用streamlit进行流式回答时,你无需手动返回文本数据,只需在方法内部使用yield关键字,并注明本次返回的内容即可。

演示视频看下吧:

image

总结

本文介绍了如何快速搭建一个基于大型语言模型(LLM)的混元聊天应用。强调了开发速度的重要性,并指出了使用Streamlit这一工具的优势,特别是对于不熟悉前端代码的开发者来说,Streamlit提供了一种快速构建聊天应用的方法。

如果你对开发感兴趣,市面上确实提供了许多大型模型供你选择。即使简单的聊天应用并不具备太多技术性,但你可以利用这些基础框架,不断添加自己所需的任何组件。这需要开拓思维,挖掘创意,让你的应用更加丰富多彩。

本文收录于
Github.com/niumoo/JavaNotes
,Java 系列文档,数据结构与算法!
本文收录于网站:
https://www.wdbyte.com/
,我的公众号:
程序猿阿朗

什么是 WebHook

WebHook 直译是网络钩子,可以把 WebHook 看做一种通知方式,只要发生关注的事件,就会发送通知到我们指定的 Web 服务。使用 WebHook 可以让我们在关注的事件发生时收到通知,而不是不断轮训 API 确认事件是否发生,

GitHub 允许我们配置 WebHook ,它允许我们配置某个仓库发生某个事件时,通知指定的外部服务。比如当指定仓库有提交代码时,GitHub 将向我们配置的 API 发送 POST 请求。以此可以实现相应的自动化操作,如持续集成 CI,请求代码审核,拉取新代码编译打包部署等。

发挥想象力,GitHub 的 WebHook 结合 GitHub Action 可以做很多事情,文末有具体例子。

这篇文章介绍 GitHub Web Hooks 的使用。

WebHook 配置

访问

访问 GitHub 仓库的 Webhooks 设置页面,打开仓库的
Settings
配置页面,点击
Webhook
Tab 页。

也可以直接访问链接:
https://github.com/用户/仓库/settings/hooks

配置

点击
Add webhook
按钮。

配置完成后,点击
Add webhook
,GitHub 会发送一个 POST 请求到配置的 web 服务用于验证是否正常。Web Hook 的事件会在请求头
X-GitHub-Event
中进行标识。

如上面说到的
ping
,会在请求头中进行标识:
X-GitHub-Event: ping
。’

一些说明:

  • Payload URL
    :此处填写你的 Web 服务地址,最好已经存在,这样
    ping
    事件才能正常响应。
  • Secret
    :安全密钥,用于对请求体进行哈希计算,用于验证是否为 GitHub 发送。后面部分会详细介绍绍。
  • SSL Verification
    :是否启用 SSL 验证,如果你的 Web 服务启用了 HTTPS,这里应该选择启用,也建议启用。
  • Just the push event
    :只订阅仓库 push 事件。

Ping 测试

点击
Add webhook
后发送一个请求到配置的 Web 服务,下面是一个真实的
ping
事件请求头信息。

Request URL: https://www.wdbyte.com/api/github/webhook
Request method: POST
Accept: */*
Content-Type: application/x-www-form-urlencoded
User-Agent: GitHub-Hookshot/eb2eabb
X-GitHub-Delivery: 10957020-e918-11ee-8a3e-e0754488dbc
X-GitHub-Event: ping
X-GitHub-Hook-ID: 468323437
X-GitHub-Hook-Installation-Target-ID: 33701056
X-GitHub-Hook-Installation-Target-Type: repository
X-Hub-Signature: sha1=1877dfa1d840bdd5583af33718cea722d825ed7
X-Hub-Signature-256: sha256=addaf81ed2f5795f06ba096b1c863b00188d3444367b5125d2036e17ca324e3

WebHook 消息验证

因为配置的 Web 服务 URL 是一个开放的 URL,任何人都可以访问,为了防止有人恶意构造 WebHook 消息请求,我们应该
对收到的请求进行验证
,判断是否为来自 GitHub Web Hook 的请求。

如何验证呢?是怎么样的一个流程呢?这时就要用到上面配置的
Secret
安全密钥了。

具体步骤如下:

  1. GitHub WebHook
    使用 Secret 对 Post Body 内容进行哈希
    (HMAC 十六进制摘要)计算,得到一个哈希值,如
    xxyyzz
  2. 将哈希值存入请求头
    X-Hub-Signature-256
    中,值以
    sha256=
    开头,如
    sha256=xxyyzz
  3. Web 服务收到请求,使用相同的 Secret 对 Post Body 进行相同哈希算法计算。得到一个摘要。
  4. 取出
    X-Hub-Signature-256
    请求头的值进行比较,如果相同则表示请求来自 GitHub Web Hook。

下面是官方给出的 JavaScript 语言的验证实现:

let encoder = new TextEncoder();

async function verifySignature(secret, header, payload) {
    let parts = header.split("=");
    let sigHex = parts[1];

    let algorithm = { name: "HMAC", hash: { name: 'SHA-256' } };

    let keyBytes = encoder.encode(secret);
    let extractable = false;
    let key = await crypto.subtle.importKey(
        "raw",
        keyBytes,
        algorithm,
        extractable,
        [ "sign", "verify" ],
    );

    let sigBytes = hexToBytes(sigHex);
    let dataBytes = encoder.encode(payload);
    let equal = await crypto.subtle.verify(
        algorithm.name,
        key,
        sigBytes,
        dataBytes,
    );

    return equal;
}

function hexToBytes(hex) {
    let len = hex.length / 2;
    let bytes = new Uint8Array(len);

    let index = 0;
    for (let i = 0; i < hex.length; i += 2) {
        let c = hex.slice(i, i + 2);
        let b = parseInt(c, 16);
        bytes[index] = b;
        index += 1;
    }

    return bytes;
}

这种对内容进行摘要计算的验证方式其实很常见,在之前介绍过的 JWT 的原理中也有提到,感兴趣的可以查看:
JSON Web Token 入门教程

注意:Secret 十分重要,应该妥善保存,防止泄漏。更不要存储到公开仓库之中。要天不知地不知,GitHub 知你知。

Java 验证 WebHook

网上有很多使用 Java 语言验证 GitHub WebHook 消息的代码实现,这里使用第三方依赖进行验证,省去哈希算法的编写。

引入依赖:

<dependency>
    <groupId>am.ik.webhook</groupId>
    <artifactId>webhook-verifier</artifactId>
    <version>0.1.2</version>
</dependency>

消息验证:

private void verify(HttpServletRequest request, String body) {
    HmacWebhookSigner webhookSigner = new HmacWebhookSigner("SHA256", secret);
    WebhookVerifier verifier = new WebhookVerifier(webhookSigner, WebhookSigner.Encoder.HEX);
    String signature = request.getHeader(WebhookHttpHeaders.X_HUB_SIGNATURE_256);
    verifier.verify(body, signature);
}

验证通过没有任何返回,如果验证失败,会抛出
WebhookAuthenticationException
异常。

WebHook 最佳实践

遵循 WebHook 最佳实践可以提高其安全性和性能,下面是一些常用建议。

  1. 只订阅关注的事件,减少事件推送次数。
  2. 使用 HTTPS 提高安全性,HTTPS 已经是 Web 服务的标配。
  3. 配置白名单或验证策略,确保消息发送方可信,比如文中提到的秘钥哈希验证。
  4. 快速响应请求,很多 WebHook 推送对响应耗时有要求,比如 GitHub 是 10 秒,因此如果你的处理逻辑过于耗时,可以考虑异步处理,优先响应。


最后推荐一下必应壁纸网站
https://bing.wdbyte.com/
,这是一个使用 GitHub Action 自动抓取壁纸,然后通过 WebHook 自动构建部署的项目,近期会对其网站进行升级,增加收藏,不同尺寸壁纸下载功能,可以蹲下我的
公众号

欢迎 Star:
https://github.com/niumoo/bing-wallpaper

相关文章:
如何使用 Github Actions 自动抓取每日必应壁纸?

本文收录于
Github.com/niumoo/JavaNotes
,Java 系列文档,数据结构与算法!
本文收录于网站:
https://www.wdbyte.com/
,我的公众号:
程序猿阿朗

今天给大家一款网站一体化测试工具:
Web-Check

Web-Check 是一款功能强大的一体化工具,用于发现网站/主机的相关信息。用于检查网页的工具,用于确保网页的正确性和可访问性。它可以帮助开发人员和网站管理员检测网页中的错误和问题,并提供修复建议。


它只需要输入一个网站就可以查看一个网站几乎所有信息,如 IP 信息、SSL、DNS记录、Cookies、域名信息、搜索爬行规则、服务器位置、重定向记录、开放端口、路由跟踪、DNS安全扩展、网站性能、关联主机名等。

Github官网地址:

https://github.com/Lissy93/web-check

Web-Check的主要用途包括:

  • 网页错误检测:Web-Check可以检测网页中的HTML、CSS和JavaScript错误,包括语法错误、标签未关闭、属性错误等,以确保网页的正确性。

  • 可访问性检查:Web-Check可以检查网页的可访问性,包括是否符合无障碍标准、是否提供足够的文本描述、是否使用正确的语义标签等,以确保网页对于残障用户的可访问性。

  • 性能优化检查:Web-Check可以检查网页的性能问题,包括加载时间、资源压缩、缓存设置等,以提供优化建议,提高网页的加载速度和用户体验。

支持的功能/特点有:

  • IP Info——IP信息
  • SSL Chain——SSL链
  • DNS Records——DNS 记录
  • Cookies—— 会话
  • Crawl Rules—— 抓取规则
  • Headers —— 请求头
  • Quality Metrics—— 质量指标
  • Server Location—— 服务器位置
  • Associated Hosts—— 关联主机
  • Redirect Chain—— 重定向链
  • TXT Records —— TXT记录
  • Server Status —— 服务器状态
  • Open Ports —— 开放端口
  • Traceroute —— 路由跟踪
  • Carbon Footprint —— 足迹
  • Server Info —— 服务器信息
  • Whois Lookup —— Whois 查询
  • Domain Info —— 域名信息
  • DNS Security Extensions —— DNS 安全扩展
  • Site Features —— 网站特征
  • HTTP Strict Transport Security —— HTTP 严格传输安全
  • Screenshot —— 截图
  • DNS Server —— DNS 服务器
  • Tech Stack —— 技术堆栈
  • Listed Pages —— 列表页面
  • Security.txt —— Security.txt
  • Linked Pages—— 链接页面
  • Social Tags—— 社交标签

安装部署:

git clone https://github.com/Lissy93/web-check.git  
cd web-check                                        
yarn install                                       
yarn build                                        
yarn serve                                         

或者直接通过Docker容器化部署:
docker run -p 3000:3000 lissy93/web-check

如果你不想安装,也可以直接访问在线地址来体验:

https://web-check.as93.net/


背景

git规定提交时必须要写提交信息,作为改动说明保存在 commit 历史中,方便回溯。规范的 log 不仅有助于他人 review,还可以有效的输出 change_log甚至对于项目的研发质量都有很大的提升。参考目前比较流行的Angular团队的commit规范



Angular commit规范格式

<type>(<scope>): <subject>
// 空一行
<body>
// 空一行
<footer>

分别对应 Commit message 的三个部分:Header,Body 和 Footer



Header

Header 部分只有一行

包括三个字段:type(必需)、scope(可选)和subject(必需)

  • type::用于说明 commit 的类型,一般有以下几种

    • feat: 新增feature
    • fix: 修复bug
    • docs: 仅仅修改了文档, 如readme.md
    • style: 仅仅是对格式进行修改,如逗号、缩进、空格等。不改变代码逻辑
    • refactor: 代码重构,没有新增功能或修复bug
    • perf: 优化相关,如提升性能、用户体验等。
    • test: 测试用例,包括单元测试、集成测试。
    • chore: 改变构建流程、或者增加依赖库、工具等。
    • revert: 版本回滚
  • scope:用于说明 commit 影响的范围

    比如:视图层、控制层、数据层、docs、config、plugin、util、test

  • subject:commit 目的的简短描述

    一般不超过50个字



Body

补充 subject 添加详细说明,可以分成多行

适当增加原因、目的等相关因素,也可不写

参考

 #  body: 72-character wrapped. This should answer:
 # * Why was this change necessary?
 # * How does it address the problem?
 # * Are there any side effects?
 # initial commit




Footer

当有当前代码与上一个版本不兼容(Breaking Change)时必须在这里描述清楚

修复的 bug(关闭issue)或是链接到相关文档,如 Closes #1, Closes #2, #3


使用

  1. 设置git 提交信息模板

新建 .gitmessage.txt(模板文件) ,参考内容如下

# headr: <type>(<scope>): <subject>
# - type: feat, fix, docs, style, refactor, test, chore
# - scope: can be empty
# - subject: start with verb (such as 'change'), 50-character line
#
# body: 72-character wrapped. This should answer:
# * Why was this change necessary?
# * How does it address the problem?
# * Are there any side effects?
#
# footer:
# - Include a link to the issue.
# - BREAKING CHANGE
#
  1. 配置提交信息模板
//这个命令只能设置当前分支的提交模板
git config commit.template   [模板文件名]
//这个命令能设置全局的提交模板,注意global前面是两杠
git config  --global commit.template   [模板文件名]


Idea 插件

  1. 安装插件
Untitled
  1. 使用
Untitled
Untitled
Untitled
Untitled

本文使用
markdown.com.cn
排版

本文介绍基于
R
语言中的
Ternary
包,绘制
三元图
(Ternary Plot)的详细方法;其中,我们就以
RGB三色分布图
为例来具体介绍。

三元图
可以从三个不同的角度反映数据的特征,因此在很多领域都得以广泛应用;如下图所示,就是一个最简单的三元图。其中,基于
R
语言中的
Ternary
包,我们可以非常方便地绘制三元图;本文就对其具体绘制方法加以介绍。

image

首先,由于我们需要用到
R
语言中的
Ternary
包,因此通过如下所示的代码配置
Ternary
包。

install.packages("Ternary")

Ternary
包提供了两种绘制三元图的方法。首先,我们可以通过其提供的
交互式界面
,通过鼠标操作完成简单的三元图的绘制工作。我们可以通过如下所示的代码打开这一交互式界面。

Ternary::TernaryApp()

交互式界面打开后,如下图所示。我们可以按照其中各个按钮、参数的介绍,手动对三元图加以修改,并最终导出图像。

我们这里主要介绍基于
代码
的绘图方法,因此交互式界面就不再过多介绍了;大家如果有需要,可以对交互式界面绘图的方法加以自行尝试。

首先,通过代码绘制三元图,我们需要导入
Ternary
包。

library(Ternary)

最简单的三元图绘制方式,就是通过图下的代码,生成一个最基本的三元图。

TernaryPlot()

运行上述代码,将得到如下所示的图片。

当然,我们需要结合实际的需求,对这个简单的三元图加以个性化修改。首先,本文所用到的全部代码如下所示。

library(Ternary)
# dev.off()

TernaryPlot(alab = "Terrain \u2192", blab = "Vegetation \u2192", clab = "\u2190 Climate",
            lab.col = c("red", "green", "blue"),
            main = "Test Ternary Plot",
            point = "up", lab.cex = 0.8, grid.minor.lines = 0,
            grid.lty = "solid", col = rgb(0.9, 0.9, 0.9), grid.col = "white",
            axis.col = rgb(0.6, 0.6, 0.6), ticks.col = rgb(0.6, 0.6, 0.6),
            axis.rotate = FALSE,
            padding = 0.08)

cols <- TernaryPointValues(rgb)
ColourTernary(cols, spectrum = NULL)

data_points <- list(
  O = c(255, 128, 0),
  Y = c(255, 255, 0),
  P = c(255, 0, 255),
  C = c(0, 255, 255)
)
AddToTernary(points, data_points, pch = 21, cex = 2.8,
             bg = vapply(data_points,
                         function (x) rgb(x[1], x[2], x[3], 255, maxColorValue = 255),
                         character(1))
             )
AddToTernary(text, data_points, names(data_points),cex = 0.8, font = 2)

legend("topright",
       legend = c("Orange", "Yellow", "Purple", "Cyan"),
       cex = 0.8, bty = "n", pch = 21, pt.cex = 1.8,
       pt.bg = c(rgb(255, 128, 0, 255, NULL, 255),
                 rgb(255, 255, 0, 255, NULL, 255),
                 rgb(255, 0, 255, 255, NULL, 255),
                 rgb(0, 255, 255, 255, NULL, 255)),
       )

运行上述代码,我们得到的最终结果图像如下图所示。

接下来,我们就对上述代码加以介绍与解释。这里需要注意,本文仅对我们用到的参数加以简单的介绍,其中很多参数的含义其实我也还没太搞清楚;大家在实际使用时,如果对参数还有其他疑惑,可以参考
Ternary
包的官方帮助文档:
https://ms609.github.io/Ternary/reference/index.html

首先,代码中的第一部分,即
TernaryPlot()
函数,就是
Ternary
包绘制三元图的基本函数;我们通过修改其中各项参数,从而修改最终成图中各个部分的属性。其中,函数的第一行,也就是
alab
开头的这一行,表示三角形三条边分别要显示的字符,也就是“
标注
”;
lab.col
则表示三角形中三条边对应的坐标轴的文字标注,需要用什么
颜色
来表示;
main
表示三元图的
标题

point
表示三角形摆放的
角度
(例如我这里的
"up"
就表示将其中的一个角朝上放置);其后的两个参数
lab.cex

grid.minor.lines
,则分别表示坐标轴的标注的
字号
与三元图的
最小单位格网线
;由于我这里是做一张三角形分别表示
R

G

B
三种颜色的颜色空间图,因此就没有设置格网线。

随后,函数第五行的
grid.lty
表示三元图格网线的
类型
,其后的参数
col
表示三元图内部的绘制
颜色
,随后的参数
grid.col
表示格网线的
颜色
(当然我这里并没有设置格网线);接下来的参数
axis.col
表示三元图三条轴的
颜色
,参数
ticks.col
则表示三条轴上对应的
标签
(不是文字标注)的颜色;参数
axis.rotate
则表示是否要
旋转标签
;最后一个
padding
参数则用以控制图片的
缩放

接下来,由于我们希望通过
R

G

B
三种颜色的填充来实现三元图的着色,因此声明一个
cols
变量,并通过
ColourTernary()
函数将其填充到三元图中。

此外,为了方便大家看图,我们希望在这个三角形的
R

G

B
颜色空间图中,添加几个关键颜色的点,作为大家看图时的参考。因此,我们通过
R

G

B
值来确定几个颜色点,将其存储在
data_point
中;随后,通过
AddToTernary()
函数将其导入三元图中。

接下来,也就是代码中的最后一部分,我们通过
legend()
函数为图片添加
图例
。这里的图例,其实就是我们在前一步骤中,为图片添加的几个主要颜色点的图例。首先,函数的第一个参数
"topright"
,表示我们希望将图例添加到图片的右上角;随后的几行参数,就是调整图例的字体、字号、要显示的内容等。

在通过调整代码,获得我们满意的三元图后,如果大家是用
RStudio
进行代码的撰写,可以在“
Plots
”中选择“
Export
”→“
Save as Image...
”选项,将图片导出;如下图所示。

不过这里需要注意,在
RStudio
中导出的图片往往清晰度不够高;如果大家希望提升图片的精度,可以选择导出
.eps
格式的图片素材;如下图所示。

随后,在
Adobe Illustrator

AI
)软件中打开刚刚保存的
.eps
格式文件;此时,不仅可以直接对图片加以进一步修改,还可以用图片格式保存更高精度的图片。

至此,大功告成。