2024年10月

游戏登录是玩家进入游戏世界的重要步骤,是玩家进入游戏后接触到的第一个可以交互的界面,它看似简单,但却意义重大。游戏登录不仅是进入游戏的必要步骤,也是保障游戏体验、数据安全和社区互动的关键环节。

HarmonyOS SDK
游戏服务
(Game Service Kit)主要提供快速、低成本构建游戏基本能力与游戏场景优化服务,有效提升游戏开发效率,帮助您进行游戏运营。游戏服务提供游戏登录能力,允许用户使用华为账号登录游戏,从而迅速推广游戏,共享华为庞大的用户价值。

游戏登录
包含使用
华为账号登录
、使用
游戏官方账号登录场景
。接入游戏登录后,游戏启动时会进行初始化,并向玩家展示联合登录面板,玩家可以选择任意一种方式进入游戏。

image
image

场景介绍

使用华为账号登录

转移场景

将玩家的游戏进度从HarmonyOS/EMUI系统渠道包转移至HarmonyOS NEXT系统包体,即将该华为账号与该游戏HarmonyOS/EMUI系统下渠道包对应的玩家标识ID的值,转移至该华为账号在该游戏HarmonyOS NEXT系统包体对应的玩家标识ID上。转移成功后,玩家可以使用华为账号进入HarmonyOS NEXT游戏,但无法再通过该华为账号进入HarmonyOS/EMUI游戏。
image
image
image

关联场景(快捷登录)

用户授权提供手机号,实现通过华为账号快速创建或关联一个游戏官方账号并进入游戏。

image
image
image

使用游戏官方账号登录

直接通过游戏官方账号登录游戏,用户体验和游戏官方包保持一致。

开发步骤

注:游戏登录涉及的具体步骤请参考
接入指南
,本文仅阐述关键开发步骤。

联合登录

初始化成功后,游戏可调用
unionLogin
接口进行联合登录,Game Service Kit向玩家展示联合登录弹框。

let context = getContext(this) as common.UIAbilityContext;
let thirdAccountInfo1: gamePlayer.ThirdAccountInfo = {
  'accountName': 'testName1', // 游戏开放给玩家接入的账号类型名字,例如"官方账号"、"xx账号"等,并不是具体某个玩家ID或开发者ID。
  'accountIcon': $r('app.media.icon') // 游戏官方账号图标资源信息
};
let request: gamePlayer.UnionLoginParam = {
  showLoginDialog: true,
  thirdAccountInfos: [
    thirdAccountInfo1    
  ]
};
try {
  gamePlayer.unionLogin(context, request).then((result: gamePlayer.UnionLoginResult) => {
    hilog.info(0x0000, 'testTag', `Succeeded in logining: ${result?.accountName}`);
  }).catch((error: BusinessError) => {
    hilog.error(0x0000, 'testTag', `Failed to login. Code: ${error.code}, message: ${error.message}`);
  });
} catch (error) {
  let err = error as BusinessError;
  hilog.error(0x0000, 'testTag', `Failed to login. Code: ${err.code}, message: ${err.message}`);
}

华为账号认证与授权

游戏判断accountName为"hw_account"时,可根据needBinding值选择通过Account Kit对应的创建授权/登录请求接口,获取用于服务器校验的Authorization Code信息。

需要华为玩家标识与游戏官方账号绑定(needBinding为true)

1.调用
createAuthorizationWithHuaweiIDRequest
创建授权请求并设置参数。

// 创建授权请求,并设置参数
let authRequest = new authentication.HuaweiIDProvider().createAuthorizationWithHuaweiIDRequest();
// 获取头像、昵称以及手机号授权需要传如下scope
authRequest.scopes = ['profile', 'phone'];
// 若开发者需要进行服务端开发,则需传如下permission获取authorizationCode
authRequest.permissions = ['serviceauthcode'];
// 用户是否需要登录授权,该值为true且用户未登录或未授权时,会拉起用户登录或授权页面
authRequest.forceAuthorization = true;
authRequest.state = util.generateRandomUUID();

2.调用
AuthenticationController
对象的
executeRequest
方法执行授权请求,并在Callback中处理授权结果,从授权结果中解析出头像昵称。

// 执行授权请求
try {
  let controller = new authentication.AuthenticationController(getContext(this));
  controller.executeRequest(authRequest, (err, data) => {
    if (err) {
      hilog.error(0x0000, 'testTag', `Failed to authenticate. Code: ${err.code}, message: ${err.message}`);
      return;
    }
    let authorizationWithHuaweiIDResponse = data as authentication.AuthorizationWithHuaweiIDResponse;
    let state = authorizationWithHuaweiIDResponse.state;
    if (state != undefined && authRequest.state != state) {
      hilog.error(0x0000, 'testTag', `Failed to authenticate. State is different.`);
      return;
    }
    hilog.info(0x0000, 'testTag', `Succeeded in authenticating.`);
    let authorizationWithHuaweiIDCredential = authorizationWithHuaweiIDResponse.data!;
    let avatarUri = authorizationWithHuaweiIDCredential.avatarUri;
    let nickName = authorizationWithHuaweiIDCredential.nickName;
    let authorizationCode = authorizationWithHuaweiIDCredential.authorizationCode;
    // 开发者处理vatarUri, nickName, authorizationCode信息
  });
} catch (error) {
  let err = error as BusinessError;
  hilog.error(0x0000, 'testTag', `Failed to authenticate. Code: ${err.code}, message: ${err.message}`);
}

不需要华为玩家标识与游戏官方账号绑定(即needBinding为false)

1.调用
createLoginWithHuaweiIDRequest
创建登录请求并设置参数。

// 创建登录请求,并设置参数
let loginRequest = new authentication.HuaweiIDProvider().createLoginWithHuaweiIDRequest();
// 当用户未登录华为账号时,是否强制拉起华为账号登录界面
loginRequest.forceLogin = true;
loginRequest.state = util.generateRandomUUID();

2.调用
AuthenticationController
对象的
executeRequest方法执行登录请求,并在Callback中处理登录结果,获取到Authorization Code。

// 执行登录请求
try {
  let controller = new authentication.AuthenticationController(getContext(this));
  controller.executeRequest(loginRequest, (err, data) => {
    if (err) {
      hilog.error(0x0000, 'testTag', `Failed to login. Code: ${err.code}, message: ${err.message}`);
      return;
    }
    let loginWithHuaweiIDResponse = data as authentication.LoginWithHuaweiIDResponse;
    let state = loginWithHuaweiIDResponse.state;
    if (state != undefined && loginRequest.state != state) {
      hilog.error(0x0000, 'testTag', `Failed to login. State is different.`);
      return;
    }
    hilog.info(0x0000, 'testTag', `Succeeded in logining.`);

    let loginWithHuaweiIDCredential = loginWithHuaweiIDResponse.data!;
    let authorizationCode = loginWithHuaweiIDCredential.authorizationCode;
    // 开发者处理authorizationCode
  });
} catch (error) {
  let err = error as BusinessError;
  hilog.error(0x0000, 'testTag', `Failed to login. Code: ${err.code}, message: ${err.message}`);
}

关联游戏官方账号

当联合登录接口获取的needBinding值为true时,游戏可调用
bindPlayer
接口绑定华为玩家标识teamPlayerId与游戏官方账号。

let context = getContext(this) as common.UIAbilityContext;
let thirdOpenId = '123xxxx'; // thirdOpenId表示游戏官方账号ID
let teamPlayerId = '456xxx'; // teamPlayerId表示玩家华为账号对应的teamPlayerId
try {
  gamePlayer.bindPlayer(context, thirdOpenId, teamPlayerId).then(() => {
    hilog.info(0x0000, 'testTag', `Succeeded in binding.`);
  }).catch((error: BusinessError) => {
    hilog.error(0x0000, 'testTag', `Failed to bind. Code: ${error.code}, message: ${error.message}`);
  });
} catch (error) {
  let err = error as BusinessError;
  hilog.error(0x0000, 'testTag', `Failed to bind. Code: ${err.code}, message: ${err.message}`);
}

未成年人防沉迷

调用
verifyLocalPlayer
接口进行账号实名认证和游戏防沉迷管控合规校验。

let context = getContext(this) as common.UIAbilityContext;
let request: gamePlayer.ThirdUserInfo = {
  thirdOpenId: '123xxxx', // 游戏官方账号ID
  isRealName: true // 玩家是否实名,该值为true时表示已实名,为false时表示未实名
};
try {
  gamePlayer.verifyLocalPlayer(context, request).then(() => {
    hilog.info(0x0000, 'testTag', `Succeeded in verifying.`);
  }).catch((error: BusinessError) => {
    hilog.error(0x0000, 'testTag', `Failed to verify. Code: ${error.code}, message: ${error.message}`);
  });
} catch (error) {
  let err = error as BusinessError;
  hilog.error(0x0000, 'testTag', `Failed to verify. Code: ${err.code}, message: ${err.message}`);
}

提交玩家角色信息

玩家成功登录游戏并选择角色、区服后,游戏需要调用
savePlayerRole
接口,将玩家角色信息上报至华为服务器。

let context = getContext(this) as common.UIAbilityContext;
let request: gamePlayer.GSKPlayerRole = {
  roleId: '123',   // 玩家角色ID,如游戏没有角色系统,请传入"0",务必不要传""和null。
  roleName: 'Jason', // 玩家角色名,如游戏没有角色系统,请传入"default",务必不要传""和null。
  serverId: '456',
  serverName: 'Zhangshan',
  gamePlayerId: '789', // 若是转移场景,请根据实际获取到的gamePlayerId传值。
  teamPlayerId: '345', // 若是关联场景,请根据实际获取到的teamPlayerId传值。
  thirdOpenId: '123'   // 若游戏官方账号登录,请根据实际获取到的thirdOpenId传值。
};
try {
  gamePlayer.savePlayerRole(context, request).then(() => {
     hilog.info(0x0000, 'testTag', `Succeeded in saving.`);
  });
} catch (error) {
  let err = error as BusinessError;
  hilog.error(0x0000, 'testTag', `Failed to save. Code: ${err.code}, message: ${err.message}`);
}

了解更多详情>>

访问
游戏服务联盟官网

获取
基础游戏服务功能开发指导文档

今天早上,OpenAI实施团队的 @shyamal在Github上开源了
Swarm
这个OpenAI官方的多智能体框架。不得不说,OpenAI官方下场,获得的社区影响就是不一样,在微信群、朋友圈里已经出现大量的解析文章。

image

这个多智能体框架确实已经把多智能体的关键,说的很透彻了,Swarm 里面定义了两个核心
「Agents」

「Handoffs」,多智能体的核心是在这个Handoffs上面。
简单看了下examples 之后我觉得这个多智能体框架并不够好,恰巧的是,我对云原生技术很熟,借用一下云原生的发展历程,给这个[Swarn]框架做个简要点评:从云原生容器发展的历史来看,相当于docker swarm 和 k8s, 我们需要的智能体框架应该是k8s 这样的一个框架,如果你是一位云原生技术熟悉的同学很容易就知道我在说什么了。

单Agent这块,简单封装提示词和使用函数调用就可以完成业务,OpenAI就一个 /api/chatcompletions 接口就帮我们搞定了,市场上大量的Agent 产品都停留在单Agent 上,但是「Handoffs」这块,Swarm的确做的非常优雅了。

swarm_diagram

个人观点认为他的设计还没有我们的多智能体框架好用,OpenAI的[Swarm]是docker swarm,我们的多智能体框架就是k8s,我需要的是像k8s编排容器那样编排智能体,我们刚刚在9月26日对外发布了多智能体的工业设计产品,详见:
智用研究院AI Agent Foundry赋能的首个多Agent驱动的工业设计平台圆满发布

多智能体的核心难题其是不同智能体之间的通信问题。怎麼传递信息,传哪些信息,这些都很重要。多个智能体协作,也只需要在必要的时候被调用起来就可以了。看我们智能体协作图:

image


当我们多智能体应用接收到用户的请求,借用Semantic kernel的设计理念叫实现“目标导向”的AI应用,这意味着它能够帮助确定目标,然后寻找实现这些目标的方法和步骤。在“目标导向”的方法中,首先需要确定目标,然后通过规划器(Planner)将目标分解为一系列需要执行的任务。这些任务可以逐个执行,以实现最终目标。这个过程对于人类来说是很自然的,但对于机器来说则相对复杂。借助LLM AI的力量,我们可以更轻松地实现这一过程。

这个接收到用户请求的智能体我们叫做路由智能体,他负责路由到具体执行任务的任务智能体。我们的智能体框架的Planner 也是类似于OpenAI的Swarm的「Handoffs」处理了交接的逻辑,我们的Planner 要比Handoffs处理的更完美。OpenAI的Swarm 目前还处于实验阶段,期望他发展成为k8s 这样的一个多智能体编排框架:

image

这个框架是python写的,大家觉得用python 写多智能体应用是好选择吗? 我个人认为做应用开发,Python并不是好选择,Python之所以用的多,是因为这一波人工智能的主导者是算法工程师,他们习惯用的编程语言是Python罢了,随着复杂场景的人工智能应用需求的增加,控制权逐步要回归到应用开发者的手中,对于复杂度高、需要长期维护的应用系统还是需要用c# 、java等业务系统开发类的编程语言来主导。

image

说明

该文章是属于OverallAuth2.0系列文章,每周更新一篇该系列文章(从0到1完成系统开发)。

该系统文章,我会尽量说的非常详细,做到不管新手、老手都能看懂。

说明:OverallAuth2.0 是一个简单、易懂、功能强大的权限+可视化流程管理系统。

友情提醒:本篇文章是属于系列文章,看该文章前,建议先看之前文章,可以更好理解项目结构。

有兴趣的朋友,请关注我吧(*^▽^*)。

关注我,学不会你来打我

为什么要用全局异常捕获?

对于一个系统来说,全局异常捕获是必不可少的,它不仅可以把异常信息精简后反馈给用户,还能帮助程序员减少解决问题的时间,以及记录系统中任何一处发生异常的信息。

你是否依然有以下苦恼?

你是否还在为怎么记录系统异常日志而苦恼?

你是否还在为系统报错位置和报错信息苦恼?

你是否还在每个接口处增加日志记录操作?

如果你有,那么本篇文章正好可以解决你的难题。

什么是全局异常捕获机制?

全局异常捕获,顾名思义就是系统无论在那个位置发生错误都会被捕获,从而进行处理。

创建接口返回模型

创建一个接口返回模型:ReceiveStatus.cs

它的主要作用是把接口返回的数据、信息推送给前端。

 /// <summary>
 ///接口返回实体模型/// </summary>
 public classReceiveStatus
{
/// <summary> ///编码/// </summary> public CodeStatuEnum code { get; set; }/// <summary> ///信息/// </summary> public string msg { get; set; }/// <summary> ///是否成功/// </summary> public bool success { get; set; }/// <summary> ///构造函数/// </summary> publicReceiveStatus()
{
code
=CodeStatuEnum.Successful;
success
= true;
msg
= "操作成功";
}
}
/// <summary> ///接口返回结果集/// </summary> /// <typeparam name="T"></typeparam> public class ReceiveStatus<T>: ReceiveStatus
{
/// <summary> ///数据/// </summary> public List<T> data { get; set; }/// <summary> ///总数量/// </summary> public int total { get; set; }
}
CodeStatuEnum.cs枚举值如下
 /// <summary>
 ///代码状态枚举/// </summary>
 public enumCodeStatuEnum
{
/// <summary> ///操作成功/// </summary> Successful = 200,/// <summary> ///警告/// </summary> Warning = 99991,/// <summary> ///操作引发错误/// </summary> Error = 99992}

创建好接口返回模型后,我们创建一个异常帮助类,它的主要用途,是区分【系统异常】还是用户自定义的【业务异常】。

/// <summary>
///异常帮助类/// </summary>
public classExceptionHelper
{
/// <summary> ///自定义异常(会写入错误日志表)/// </summary> /// <param name="msg"></param> public static void ThrowBusinessException(stringmsg)
{
throw newException(msg);
}
/// <summary> ///自定义业务异常(不会写入错误日志表)/// </summary> /// <param name="msg">信息信息</param> /// <param name="codeStatu">异常状态</param> /// <returns>返回结果集</returns> public static ReceiveStatus CustomException(string msg, CodeStatuEnum codeStatu =CodeStatuEnum.Warning)
{
ReceiveStatus receiveStatus
= new();
receiveStatus.code
=codeStatu;
receiveStatus.msg
=msg;
receiveStatus.success
= false;returnreceiveStatus;
}

}
/// <summary> ///异常帮助类(返回数据)/// </summary> /// <typeparam name="T"></typeparam> public class ExceptionHelper<T>: ExceptionHelper
{
/// <summary> ///自定义业务异常(不会写入错误日志表)/// </summary> /// <param name="msg">信息信息</param> /// <param name="codeStatu">异常状态</param> /// <returns>返回结果集</returns> public static ReceiveStatus<T> CustomExceptionData(string msg, CodeStatuEnum codeStatu =CodeStatuEnum.Warning)
{
ReceiveStatus
<T> receiveStatus = new();
receiveStatus.code
=codeStatu;
receiveStatus.msg
=msg;
receiveStatus.success
= false;
receiveStatus.data
= new System.Collections.Generic.List<T>();returnreceiveStatus;
}
}

创建全局异常捕获中间件

在wenApi启动项目中创建一个类:ExceptionPlugIn.cs

它的主要作用就是捕获系统中发生异常对代码和记录异常日志。

它需要继承一个接口:IAsyncExceptionFilter

/// <summary>
///全局异常捕获中间件/// </summary>
public classExceptionPlugIn : IAsyncExceptionFilter
{
/// <summary> ///全局异常捕获接口/// </summary> /// <param name="context"></param> /// <returns></returns> publicTask OnExceptionAsync(ExceptionContext context)
{
//异常信息 Exception ex =context.Exception;//异常位置 var DisplayName =context.ActionDescriptor.DisplayName;//异常行号 int lineNumber = 0;const string lineSearch = ":line";var index =ex.StackTrace.LastIndexOf(lineSearch);if (index != -1)
{
var lineNumberText = ex.StackTrace.Substring(index +lineSearch.Length);
lineNumber
= Convert.ToInt32(lineNumberText.Substring(0, lineNumberText.IndexOf("\r\n")));
}
//如果异常没有被处理则进行处理 if (context.ExceptionHandled == false)
{
string exceptionMsg = "错误位置:" + DisplayName + "\r\n" + "错误行号:" + lineNumber + "\r\n" + "错误信息:" +ex.Message;//定义返回类型 var result = new ReceiveStatus<string>{
code
=CodeStatuEnum.Error,
msg
= "错误信息:" +exceptionMsg,
success
= false,
};
context.Result
= newContentResult
{
//返回状态码设置为200,表示 StatusCode =StatusCodes.Status500InternalServerError,//设置返回格式 ContentType = "application/json;charset=utf-8",
Content
=JsonConvert.SerializeObject(result)
};
//记录日志 }//设置为true,表示异常已经被处理了 context.ExceptionHandled = true;returnTask.CompletedTask;
}
}

可以在OnExceptionAsync方法中添加记录日志、异常类型、异常分析等代码。

添加到服务中

编写好异常捕获机制后,我们需要把该类添加到Program.cs的服务中

//自定义全局异常处理
builder.Services.AddControllers(a =>{
a.Filters.Add(
typeof(ExceptionPlugIn));
});

测试全局异常捕获机制

添加一个异常测试接口

运行测试

以上就是全局异常捕获机制,感兴趣的可以下载项目,修改吧。

源代码地址:https://gitee.com/yangguangchenjie/overall-auth2.0-web-api

预览地址:http://139.155.137.144:8880/swagger/index.html

帮我Star,谢谢。

有兴趣的朋友,请关注我微信公众号吧(*^▽^*)。

关注我:一个全栈多端的宝藏博主,定时分享技术文章,不定时分享开源项目。关注我,带你认识不一样的程序世界

plsql是什么:

就是这个,专门操作oracle的一个工具,好用还免费。

创建一个测试表:

create table Student(
Id number not
null,
Name varchar(
20),
Age number,
Grade number,
Gender varchar(
2)
)

里面的varchar2()是oracle自己专门的字符类型,用就行了。

光标移到表上,右键选择Describe:

现在这些字段都没有说明,不知道是什么意思,给他们都添加说明

comment on table Student is '学生表';
comment on column Student.id
is 'ID';
comment on column Student.Name
is '姓名';
comment on column Student.Age
is '年龄';
comment on column Student.Grade
is '年纪';
comment on column Student.Gender
is '性别';

添加一条测试数据

添加多条数据,但是不写insert

在后面输入一个for update,上面的操作栏会显示有可以提交的事务,先不用管,然后现在点击一下下面的锁

oracle会生成一个空白行,然后前面带有一个✳,我们先选中我们添加的那一行数据:

然后复制一下,复制以后再选中下一行,不停的粘贴就行了

然后改一下数据,最后点击一下那个绿色的小勾,再点一下绿色的锁,最后我们去点一下菜单栏的提交事务按钮

执行完毕以后点击查询就可以了:

如果只想执行某一段代码,可以用鼠标选中自己想执行的代码就行了,如图所示,后面的for update就没有执行;

如果想更新某个字段,也可以直接通过上面的步骤操作,有点像在操作excel的感觉;

如果想删除,也和上面的操作类似,只不过是点击的按钮不一样;

执行以后,刘德华就会被删除。

数据的导出:

可以选中行,按住ctrl可以选多行.

在粘贴板上就会把sql语句粘贴进去:

删掉多余的,只保留insert部分就可以了。

怎么看我们最开始的建表语句了:

点击  view

右下角有一个view sql的按钮,点一下

点进去就可以看到建表语句了,复制出来保存就行了。

暂时只想到这些

下面是一些常用的查询语句

select * from student t where instr(t.name, '') > 0; --模糊查询select *
  fromstudent twhere (t.name = '刘德华' and t.age = '50')
or t.name
= '梁朝伟'; --多个条件的查询select t.*,casewhen t.gender= ''then'帅哥'when t.gender= ''then'美女' else '不知道'end p--查询的时候条件判断fromstudent t;select t.*, decode(t.name, '刘德华', '我最喜欢的明星', '明星') --判断fromstudent t;select t.*, nvl(t.name, '非主流') from student t; --判断名字是不是空

select wm_concat(t.name) from student t --合并多行的某条数据,可以配合group by

QQ技术交流群:332035933;

有向图
和上一篇介绍的
无向图
基本一样,唯一的区别在于
有向图
的边有方向性,它表示的是顶点之间的单向或依赖关系。

有向图
G
一般表示为:
G=<V,E>
。和无向图一样,
V
是顶点集合,
E
是边的集合。

不同之处在于,
无向图
是用小括号
(V,E)

有向图
用尖括号
<V,E>


有向图
中,边是有方向的,所以,从
顶点A到顶点B
的边与从
顶点B到顶点A
的边是
不同
的。


无向图
一样,
有向图
也有很多应用场景,比如:


地图导航
中,
有向图
常被用来表示道路网络。

节点代表地点(如交叉路口、城市等),有向边代表道路,边的权重可以表示道路的长度、行驶时间或交通状况等。


供应链管理
中,有向图可以用来表示货物的流动路径。

节点代表供应链中的各个环节(如供应商、制造商、分销商等),边代表货物流动的路径,边的容量可以表示货物的承载能力。


社会网络
中,上一篇提到可以用无向图表示用户之间的好友关系。

而有向图同样可以用在社会网络分析,它可以用来表示用户之间的关注关系,转发关系等,用于分析用户的行为模式。

下面介绍
manim
中绘制
有向图
的对象
DiGraph

1. 主要参数

有向图对象
DiGraph
主要参数和无向图类似:

参数名称 类型 说明
vertices list 图的顶点列表
edges list 图的边列表,每个边
labels dict 顶点是否显示标签文本
label_fill_color str 标签的背景色
layout str 图中定点的布局方式
layout_config dict 配置如何布局图中各个顶点
layout_scale float 图各个顶点布局的比例
vertex_type Mobject 顶点的类型,不一定是点,也可以是manim中其他的对象
vertex_config dict 顶点相关的配置
vertex_mobjects dict 一系列的顶点对象
edge_type Mobject 边的类型,不一定是线,也可以是manim中其他的对象
edge_config dict 边相关的配置
paritions list
root_vertex dict

这些参数中,
vertices

edges
相关的参数(比如xxx_type,xxx_config)比较好理解。

labels
参数设置是否需要显示顶点的标签,默认是把
vertices
的数值作为标签的内容。

layout
参数内置了多种现成的布局方式:

  • 'circular',
  • 'kamada_kawai'
  • 'partite'
  • 'planar'
  • 'random'
  • 'shell'
  • 'spectral'
  • 'spiral'
  • 'spring'
  • 'tree'

layout_config
参数可以对上面现成布局方式的进行微调。

最后两个参数
paritions

root_vertex
比较特殊,

paritions
只能在
layout
设置为
'partite'
时使用,用来生成层状的图(比如描述神经网络的图),

paritions
用来设置每一层包含哪些顶点;

root_vertex
只能在
layout
设置为
'tree'
时使用,用来树状图,

root_vertex
用来设置树的根节点。

后面的示例会演示如何使用
paritions

root_vertex
来生成
层状

树状

有向图

2. 主要方法

有向图
DiGraph
的方法主要用来动态改变有向图,比如添加或删除顶点和边。

名称 说明
add_edges 增加有向图的边
add_vertices 增加有向图的顶点
remove_edges 删除有向图的边
remove_vertices 删除有向图的顶点
change_layout 动态改表有向图的结构
from_networkx
networkx
来生成有向图

networkx
是另一个常用的
Python
库,用于创建、操作和研究复杂网络的结构。

DiGraph
对象也可以直接根据
networkx
的对象生成图。

3. 使用示例

下面的示例和上一篇无向图的示例类似,只是改用有向图
DiGraph
对象来实现。

3.1. 顶点的配置

顶点的设置和无向图几乎是一样的。

# 不同颜色的设置
graph = DiGraph(
    vertex_config={
        0: {"color": RED},
        # ...
    },
)

# 顶点显示标签
graph = DiGraph(
    labels=True,
)

# 星形顶点
graph = DiGraph(
    vertex_config={"outer_radius": 0.15},
    vertex_type=Star,
)

3.2. 边的配置

有向图的边也和顶点一样,可以设置颜色,粗细等属性,

与无向图不同之处在于:有向图的边可以设置箭头的样式。

# 边的颜色
graph = DiGraph(
    edge_config={
        (0, 1): {"color": RED},
        # ...
    },
)

# 边的粗细
graph = DiGraph(
    edge_config={
        (0, 1): {"stroke_width": 1},
        # ...
    },
)

# 不同箭头的边
graph = DiGraph(
    edge_config={
        (0, 1): {
            "tip_config": {
                "tip_shape": ArrowCircleTip,
            },
        },
        (0, 2): {
            "tip_config": {
                "tip_shape": ArrowTriangleTip,
            },
        },
        # ...
    },
)

3.3. 内置的layout

有向图
中内置的layout和上一篇无向图中介绍的是一样的。

for layout in [
    "spring",
    "circular",
    "kamada_kawai",
    "planar",
    "random",
    "shell",
    "spectral",
    "spiral",
]:
    graph = DiGraph(
        layout=layout,
    )

3.4. 层状图

层状图的布局需要配合参数
partitions
一起使用,
partitions
中决定每一层中有哪些顶点。

有向图的边有方向,绘制出来更像神经网络的结构。

partitions = [[0, 1], [2, 3, 4], [5, 6], [7, 8]]
graph = DiGraph(
    layout="partite",
    partitions=partitions,
)

3.5. 树状图

树状图的布局需要配合参数
root_vertex
一起使用,
root_vertex
定义了树的
根顶点
是哪个。

这里与
无向图
有个不同的地方,绘制有向的树状图时,顶点和边的顺序很重要,需要从根节点开始,依次传入各个顶点。

下面示例中,第二个树状图改变了 根节点,不是仅仅改变
root_vertex
就行的,需要先改变图中顶点的顺序。

下面的代码是简略后的代码,完整的代码可以文中最后部分的链接中下载。

# 初始的树
graph = DiGraph(
    layout="tree",
    root_vertex=0,
)

# 重要!!! 
# 修改前需要调整节点和边的顺序

# 修改根节点
graph2 = DiGraph(
    layout="tree",
    root_vertex=2,
)

4. 附件

文中完整的代码放在网盘中了(
digraph.py
),

下载地址:
完整代码
(访问密码: 6872)