To 注释 or not 注释, that is a question

本文探讨了程序注释的利弊,认为过多的注释反而会影响代码的可维护性。提出内部实现上应少写注释,重点介绍了有价值的注释类型及替代方案。
“程序里的注释是多好还是少好”,“一个合格的程序员是否应该多写注释”。我参与到这个话题是因为[url=http://www.iteye.com/news/26564]《优秀的程序 vs. 糟糕的程序》[/url]这篇资讯译文。去看了一下原文,作者显然是蹲坑时无聊想出来几句打油诗,说的都是业界早有定论的,虚不拉几的东西,例如什么“优秀的程序容易维护,糟糕的程序很难维护”云云。根本无意在这种打油诗里说些有争议的话题。没想到被翻译发表在iteye里,由于不押韵,看起来倒好像是几条最高指导原则一样。更没想到一句[color=blue][b]“优秀的程序不需要加以说明;糟糕的程序需要大量注释。”[/b][/color]惹起了诸多讨论。令我惊诧的是,还有这么多的程序员对这个我一直以为早有定论的东西持不同意见,聊得不亦乐乎。那与其在别人的地方歪楼,坏了人家打油诗的意境,倒不如在我自己的地盘上写篇东西详细讨论一下。

PS. 那句的原话是:Good programming is self-explanatory. Bad Programming requires explanation。直译的话应该是:优秀的程序能自我解释,糟糕的程序的需要(额外的)解释(自然包括用大量注释来解释)。恐怕是因为在中文里explanation和interpreting(与“编译”所对应的“解释”)都译成“解释”,在IT文章里怕有歧义,结果把注释(comment)给扯进来了。真是躺枪呀,本来是没它什么事的……

先表明一下个人立场,我的观点是,如果真正想项目容易维护,那么在公共API的位置上,要多写文档性的注释(其实应该说是“注释形式的文档”),[b]而在内部实现上,要少写注释[/b],让程序自我表达。而在程序内部,只有以下几种注释是有价值和可维护的:

1. [b]TODO和FIXME[/b]

2. [b]在非常规的做法上,或者考虑过多种处理方式而最终选择其中一种的地方,记下为什么不采用其他似乎更好的方式,说明已经考虑过这种方案了。[/b](最典型就是在吞掉异常的地方写清楚为什么要吞掉)

3. [b]在被注释掉的代码上说明这段代码的功能,以及为什么要注释掉。[/b](被注释掉的代码没有语法高亮,不方便直接阅读。而且不在测试覆盖)

简单来说,[color=red]内部实现上的注释,主要应该记录“没做”和“决定了不去做”这类根本没有具体代码与之相对应的内容。[/color]

除此之外,出现在内部主线实现上的注释都有各种副作用(强调一下,是个人观点),注释得越详细,文采越好,副作用越大,越影响维护。而且,内部注释的绝大部分功能,在当今的技术条件下,有更好的替代方案。与其使用具有副作用的注释,不如转变习惯,改用没有副作用的方式。

下面展开来聊一聊,在没有特别说明的情况下,“注释”均指内部实现上的注释,不包括在API接口上,类似JavaDoc这种“用注释的格式编写的文档”。

[size=large][b]注释存在的问题[/b][/size]

[size=medium][b]第一个问题:写注释的机会成本[/b][/size]

做管理的人都清楚,一件事情该不该去做,除了考虑直接的成本和收益,还要考虑[url=http://wiki.mbalib.com/wiki/%E6%9C%BA%E4%BC%9A%E6%88%90%E6%9C%AC]机会成本[/url]。也就是如果把做这一件事的资源用来做其他事情,收益会不会更大。

相信你也猜到我想说什么了。Ok,如果你说你把本来用来嘿咻的时间用来写注释了,那请跳过本节。但如果你是在工作时间里埋头写注释,那就应该认真考虑一下这个问题了。

喜欢写注释的人经常会幻想自己写注释是因为自己比别人更勤奋,更负责,更有团队意识,更有牺牲精神。而别人不写注释是因为他们更懒散,更自私。而实际情况是,他们有空闲写注释,是因为他们把自己的产出量跟新手们看齐后才腾出的工作时间。而这些时间,他们本来可以用来编写新功能,或者把代码重构成自描述的,或者写写真正的文档或者教程。

退一步来说,就算你能说服管理者把你“写注释”当成预算内的资源消耗(或者那个管理者就是你自己),那内部注释还有另一个机会成本的问题:既然你已经耗用了大量时间去写注释,自然应该把收益最大化,也就是,这段注释的时效应该尽量长(关于注释的时效问题下面会谈到),应该能被更多人看到。那么,这段注释就应该放在接口API上,成为API文档。这样就能一定程度上保证这段注释更容易被重视,被维护,被其他人看到。

你或许要问,那我的注释就是针对一段内部代码的,放在接口文档上岂不是成了实现细节?这个问题很好解决,把段代码抽取出来,形成一个独立的方法,再给这个方法写API文档。

那岂不是写注释侵入了我的程序结构吗,我犯不着为每一个写注释的地方都抽取一个独立方法呀。恰恰相反,[color=darkblue]既然你能为一段内部代码写一段注释来描述其功能。那说明这段代码的相对来说功能是独立的。[/color]你不把它抽取出来,才是懒散的表现。

在这里举一个真实的例子,这是我之前在论坛上和某位朋友讨论(好吧,争论)的时候他贴出来作为论据的。在他看来,他写的这段注释自然堪称经典。而在我看来,也是颇为经典————的反面教材。(为了避免扩大争论,就不给出链接了,怕被断章取义的朋友可以私信找我要链接。如果那位跟我讨论朋友路过看到,也可以要求我贴出原文链接)

[img]http://dl.iteye.com/upload/attachment/0076/9814/94445b92-e1b7-3e43-9d3a-f8f014af8c9b.png[/img]

先不考虑他写这段注释的时间可以干多少其他活。我[b]个人[/b]的看法,这段注释本身的第一个问题就是,写得这么工整,却放在了方法内部,谁能看得到。只有两种人有机会看到这段注释,一是没事干通读代码熟悉系统的新人,但在复杂的代码库里,他刚好跑到这里来看到这段注释的机会有多少?二是排错时跟踪到这里的人,但这时对他真正有意义的信息(可能)是currentRowIndex为什么在这里为null,在原本的设计意图里,它在这里能否允许为null,而不是这种关于使用方法的描述。

[size=medium][b]第二个问题:注释的准确性和信任危机[/b][/size]

对于前面一个问题,不少人的回答是,如果我耗费一个小时的时间来写注释,能省下别人数十个小时的时间,何乐而不为?

首先,[color=red]要省别人或自己的时间,写注释并不是是唯一的方法,也不是最好的方法[/color](这个后面会谈)。而只不过是最方便,和最没有任何其他附加价值的方法(强调一下,API文档注释不在此列)。没有其他附加价值有什么好处?好处是它最能满足我们具有牺牲精神的浪漫情怀,最能说明我们有多重视程序的可读性。“你看我为了你们这帮小子看代码容易点,白白耗了多少时间来写注释。你们不好好读我的代码对得住我吗?”

其次,虽然一个拥有详尽,准确注释的代码库显然更容易理解。[color=red]但前提是,这些注释都是准确的[/color]。准确的意思是:[color=darkblue]注释的内容应该能正确说明出它看上去所要注释的代码某方面的特性[/color]。

(“它看上去所要注释的代码”?太绕口了吧。嗯,我就见过在在同一个文件里,应该有两个以上的人修改过,有的注释是写在要注释的代码前面一行的,有的注释是写在要注释的代码后面一行的。虽然它们都在准确地注释它们所要注释的代码,但对任意一种阅读习惯的读者来说,看上去总有一部分注释和代码是不匹配的。)

注释有可能不准确吗?太有可能了!首先,从写注释的那一刻开始,注释就受到程序员对代码的理解、他的语言文字表达能力,以及他当时的精神状态影响。关精神状态什么事?在疲劳的时候,错别字你总写过吧,把“或”跟“和”搞混总试过吧,写漏“不”字你总试过吧。其次,即使代码在创建的那一刻是准确的,随着时间的推移,它也有很大机会变得不准确,我称这为“注释的时效性”。如果不维护,在发展变化的代码库里,注释会慢慢失效。

注释失效是很容易发生的,常见的情景有:

[b]1. 代码被更改了,但没有更改注释。[/b]很多人想象不到这有多轻易就能发生:代码太简单,不用看注释就弄懂了,根本就没留意有注释;注释太简单,改完代码就忘了;注释太隆重,不敢改(回想一下前面的那个例子,这是另一个程序员给未来阅读他的类的人的一封信,我改了要不要署名?会不会有法律问题?);注释太长,不想改;心情不好,不改(反正也没人监督)。

[b]2. 注释连同代码一起被复制然后更改了代码,但没有改注释。[/b]觉得自己是核心架构师,写的核心代码万年不变的朋友,你是逃不掉的……

[b]3. 注释的上下文被更改了,注释的含义发生了变化。[/b]这个比较阴,我记得是遇到过,但一时又想不起来具体场景,想起了再补上。成因是,自然语言的表达是连贯性的,上下文之间有时需要结合来理解才能清晰。有可能在改代码时,虽然程序员很负责,同时修改或删除了代码对应的注释,但却无意间引起了后文意义的改变或者形成歧义。

现在不妨回头看看上面的例子,如果你没有被一堆的文字吓住,认真去看看注释内容,你会发现,里面提到的变量名称在紧跟着的代码上都找不到!注释中说的是某个Map,下面的代码却是个List。他说“下面这行代码中”,而下面紧跟的那行代码跟他说的东西一点关系都没有。对于这个,我没什么好说的了,只能在这里保证,绝对是原文截图,而不是为了写这篇文章杜撰的。

好吧,注释失效就失效了,它总算发过光发过热了。失效的注释放在那里,很快就会被人删掉或者改正的。如果你这样想,就大错特错了。除非你的团队里都是初出茅庐的愣头青,否则[b]在团队环境中保住脑袋的首要原则就是:不要随便改动你不知道是干什么的东西,特别是当这个东西跟你手上的任务无关时。[/b]而团队环境的第一定律是:如果一个你不知道干什么的东西又看上去没有跟任何其它东西发生直接关联,你永远也弄不清它是干什么的(好吧,也不是不可能,只不过很难)。试想一下,你在排错的过程中会不会留意到一条跟前后左右代码都不相关的注释。如果你不幸认真阅读了它,并且被它误导了,等你认真阅读了代码,你得有多大自信和责任心才敢改动它:首先,在复杂的系统里,你怎么敢肯定你现在的理解就是正确的。其次,你怎么能确定别人看起来时你的说法会比原来的说法更清楚。最重要的是,如果你不改,没人会怪你,如果你把它改对了,没人会激励你(除非你改完后自己到处说,然后别人说声,嗯,不错),如果你把它改错了又误导了其它人,提交历史记录上写的就是你的名字。这个厉害关系有点脑子的人都会算。

PS. 每次我说这种话,就肯定会有人跳出来说:最恨就是你这种不负责任的人。再讨论几个回合之后,这人就会说出来:我之所以觉得你这种人这么讨厌,是因为我上一个公司/生意/项目就是被你这种人搞砸的!好吧,我的观点是,你之所以搞砸,就是因为你的管理方式脆弱到只要有一个员工不负责,整个公司/生意/项目就会垮掉。你想让它活的长点,至少应该让它能适应10%的员工不如你想象中负责的情况吧。

总之,跟代码不同,注释没有编译器语法检查,没有单元测试,没有QA把关。你唯一的指望,就是团队里有一个表达能力超强,工作清闲,且不怕得罪人的热血骨干来维护。

好吧,就让失效注释留在那里。有什么问题?想象一下有A,B,C三个人,A只说真话,B只说假话,C有时说真话,有时说假话,而且你不知道他哪一句是真的,你觉得谁最讨厌?就我来说,最讨厌的是C!A自然是好兄弟,至于B我就当他的话是耳边风,谈不上讨厌。但C,如果你不相信他的话,浪费了额外的时间,结果证明他说的是真的,人人都来说你没事找事,再给你扣个看不起人的帽子。如果你相信他的话,不知什么时候就被阴一下,别人照样说你笨。你去骂他,他给你来一句“我也就是想帮帮你而已”,你就自行判断他这句话的真假吧。一堆夹杂着失效和有效注释的代码,就是这位C先生。别人我不好说,至少我对这种人,只好假定他每一句都是假的,对他说的所有话都不轻信。[b]这就出现了一个很无奈的矛盾:所有与代码对应的注释,我先要理解这段代码,才能确定这段注释是准确的,而这段注释的唯一作用,就是帮助我理解这段代码。[/b]

换句话说,[b]内部注释存在“短板效应”,整个系统里所有注释的可信性受到其中最不可信的那部分注释的限制[/b]

你会问,那照这么说,前面所说的几种所谓有价值的注释也有这个问题,API上的注释文档也有这个问题呀。严格来说,是的,但相对来说没那么严重。那几种注释都不对应具体的执行代码,不存在矛盾的问题。而你不阅读他们,就无法开始工作。一旦开始工作,就可以直接把它们删除掉,很容易作为规定来推行。唯一可能出现矛盾的地方,就是有人把缺的东西补进去了,但没有删除注释。这种情况危害较小,因为很少有人不经过项目组统筹规划,看到程序里写着TODO就傻乎乎的DO起来。

至于API文档,代码行为如果与文档不对应,很快就会有人发现,而且责任很清晰,就是改代码的人的责任,他有压力去主动维护。最重要是,这些API文档会形成即时代码提示,具有不可替代的特殊功能。

还有人会说,你杞人忧天而已,我可以选择直接相信注释,就算失效,大不了浪费一点时间,只要系统里有效注释足够多,平均下来,注释还是为我节省了大量时间。可惜,事实上,在排错过程中,失效注释带来的损失往往不止一点,要知道,真正的排错并不是找到离故障位置最近的最后一个出错的地方,然后把它调整正确这么简单(这样做往往是通过引入一个新的错误来修正之前的一个错误)。 而是需要推导原本的设计意图,在整条逻辑链最早出现错误的位置进行修正。在推导逻辑链过程中,是一环扣一环的,后面的所有推导,都是基于前面推导所形成的结论。如果前面的结论错了,那么后面的一切推导都是无用功,幸运的话会进入死胡同,倒霉的话就找到一个似是而非的地方,改动之后把当前问题解决了,但对其他分支却引入了新的问题。而且,人的工作情绪是很容易受到返工打击的,以我来说,如果推导一直正确的话,我可以一直排查一整天。但是如果在推导过程中发现前面某一步犯了个低级错误把整个逻辑链推倒重来的话,两个小时就会让我疲惫不堪。

不妨想象一下,你家电视机不亮了,你去排查,当然首先就是检查唯一的电源插座,然后你看到插座上贴着一张纸:24小时连续不间断无故障供电。于是你就开始拆机壳,把东西拆完楞是没找到问题。直到天黑,你找来一个本来亮着的台灯插在原本电视机的插座上,这时你发现台灯不亮。你现在是什么心情。别忘了,你的任务是让电视机亮起来。找出问题出在插座上是不够的,这是唯一的插座,你现在应该开始拆插座了。哦,别忘了先把电视机装起来。

[size=medium]第三个问题:歧义[/size]

如果说,失效的注释会导致时间的浪费,那么歧义的注释就甚至可能导致直接的损失了。关于我们日常所用的自然语言有多容易产生歧义就不多说了。在这里借用温伯格老先生在《你的灯亮着吗》中举的一个例子:

[quote]

谈到容易误解的词语、放错了地方的逗号或者一个表意不明确的语法,以及由它们造成的一万、十万、一百万或者你所能想象的足够多钞票的损失,任何一个计算机程序员都能提供十几个例子。

例如,一段程序注释上写着,

“异常信息也会出现在文件 XYZ 中。”The exception information will be in the XYZ file, too.)

某个程序员理解为,

“这条异常信息还会在文件 XYZ 的另一个地方出现。”(Another place the exception information appears is the XYZ file.)

于是他认为这条异常信息在别的地方还有备份,因此他觉得他的程序中没有必要保留它。

事实上,写这条注释的人的意思是,“XYZ 文件中有一种信息也属于异常信息。”Another type of information that appears in the XYZ file is the exception information.)

这句话并不表示这条信息在别的地方有备份,并且事实上,并没有什么备份。结果,这条宝贵的、不能复原的信息就这样丢失了。在人们发现对这句注释有不同的理解之前,这条丢失了的异常信息造成的损失已经达到 50 万美元了――对于一个因为考虑不周全而加的“也”字来说,这损失未免太惨重了。

50 万美元就这样飞了,一定会有人掉脑袋。但是,应该砍谁的头呢?写注释的那个人?还是程序员?大多数英语课堂上会把写注释的家伙推上断头台。而教人解决问题的老师则会把这个程序员的脖子放在断头台上。有没有人喜欢不流血的方法?

我们可以告诫那些写注释的人,对于问题表述来说清晰好懂是多么的重要,直到他们被这废话的海洋淹死。我们也可以敦促问题解决者们阅读的时候更加仔细,然后他们都会变成瞎子。[b][color=red]按照以往的经验,这些都没什么用。不管人们多么真诚地去努力,单靠增加投入精力的数量是不够的。你永远都不能确信这里的每个人对于同一个词都和你有相同的理解。[/color][/b]

[/quote]

在书中,温伯格只是提出了问题,没有给出具体解决的办法。这种事情,甚至连责任该落到谁头上都是公说公有理,婆说婆有理。唯一可以肯定的是,无论最后责任归到谁头上,那人都是一肚子的冤枉。那我们不妨想想,为了避免自己受这种冤枉气,我们该怎么办?至少我个人得出的结论是,[b]如果我是写程序的那个家伙,我就干脆不要写那条注释。如果我是读程序那个家伙,我就干脆不要管那条注释。[/b]

[size=medium]第四个问题:救生圈效应[/size]

这是我自己临时想出来的一个说法,什么是救生圈效应呢。很多大人都知道,如果要教小孩子学游泳,最简单有效的办法是直接把他扔到水里,虽然在最开始的时候,你要时刻注意着他,在必要时才扶一把。但等他呛几口水后,很快就自然学会狗扒式了。这时再去教什么蛙式自由式就简单得多了。但如果你一开始给他一个救生圈,虽然你就算一直不管他也能自己游个十几米,但他永远学不会自己游。[b]而最大的问题是,并不是任何时候你都能找到救生圈。[/b]

那些觉得在程序里写满注释然后交给新手,幻想自己在帮助新手进步的朋友,有没有想过这些注释就是救生圈?而那些你教导出来的程序员总会有一天在论坛上发帖骂娘:我刚入职的某某公司的代码里TMD没有一行注释!在我听起来,这种抱怨就像一个快溺水的人在喊海里怎么没有救生圈,这能怪海吗?

内部注释在心理上的救生圈效应则更为明显,是一个非常好的推卸责任的皮球。写程序人觉得程序本身不太清晰,没关系,我的注释写清楚了。出了问题,那是修改的人没好好维护我的注释,读程序人没好好理解我的注释。读程序的人觉得程序不太理解,没关系,直接相信注释就行了,出了问题,那是有一条不知道是谁写的注释TMD骗了老子,只能怪老子倒霉了。

假如你是项目经理,有A和B两个人让你选。A写的代码是自描述的,结构清晰,但没有一行内部注释。B写的代码一团浆糊,但注释详细,代码连着注释看尚算能懂。排错时,如果是带有完善准确注释的代码,A比B慢一倍(实际上可未必),如果是没有注释或注释有错的代码,A的速度不变,B比A慢几倍,甚至完全卡住(临急抱佛脚看代码的结果)。你会选谁?


前面说了那么多注释的问题,但除了注释,还有没有其他能帮助我们理解程序,又没有上述种种问题的方式呢。如果到了最后,注释是唯一的选择,那就没什么好说的了。下面就开始聊聊内部注释的几个种类,和它们的替代方案。不过今天夜了,且听[url=http://kidneyball.iteye.com/blog/1735698]下回分解[/url]吧。
翻译成中文,只做翻译:8.2. Simultaneous Probe Tiebreaking The astute reader will observe that there is a race condition inherent in the previous description. If two hosts are probing for the same name simultaneously, neither will receive any response to the probe, and the hosts could incorrectly conclude that they may both proceed to use the name. To break this symmetry, each host populates the query message's Authority Section with the record or records with the rdata that it would be proposing to use, should its probing be successful. The Authority Section is being used here in a way analogous to the way it is used as the "Update Section" in a DNS Update message [RFC2136] [RFC3007]. When a host is probing for a group of related records with the same name (e.g., the SRV and TXT record describing a DNS-SD service), only a single question need be placed in the Question Section, since query type "ANY" (255) is used, which will elicit answers for all records with that name. However, for tiebreaking to work correctly in all cases, the Authority Section must contain *all* the records and proposed rdata being probed for uniqueness. When a host that is probing for a record sees another host issue a query for the same record, it consults the Authority Section of that query. If it finds any resource record(s) there which answers the query, then it compares the data of that (those) resource record(s) with its own tentative data. We consider first the simple case of a host probing for a single record, receiving a simultaneous probe from another host also probing for a single record. The two records are compared and the lexicographically later data wins. This means that if the host finds that its own data is lexicographically later, it simply ignores the other host's probe. If the host finds that its own data is lexicographically earlier, then it defers to the winning host by waiting one second, and then begins probing for this record again. The logic for waiting one second and then trying again is to guard against stale probe packets on the network (possibly even stale probe packets sent moments ago by this host itself, before some configuration change, which may be echoed back after a short delay by some Ethernet switches and some 802.11 base stations). If the winning simultaneous probe was from a real other host on the network, then after one second it will have completed its probing, and will answer subsequent probes. If the apparently winning simultaneous probe was in fact just an old stale packet on the network (maybe from the host itself), then when it retries its probing in one second, its probes will go unanswered, and it will successfully claim the name. The determination of "lexicographically later" is performed by first comparing the record class (excluding the cache-flush bit described in Section 10.2), then the record type, then raw comparison of the binary content of the rdata without regard for meaning or structure. If the record classes differ, then the numerically greater class is considered "lexicographically later". Otherwise, if the record types differ, then the numerically greater type is considered "lexicographically later". If the rrtype and rrclass both match, then the rdata is compared. In the case of resource records containing rdata that is subject to name compression [RFC1035], the names MUST be uncompressed before comparison. (The details of how a particular name is compressed is an artifact of how and where the record is written into the DNS message; it is not an intrinsic property of the resource record itself.) The bytes of the raw uncompressed rdata are compared in turn, interpreting the bytes as eight-bit UNSIGNED values, until a byte is found whose value is greater than that of its counterpart (in which case, the rdata whose byte has the greater value is deemed lexicographically later) or one of the resource records runs out of rdata (in which case, the resource record which still has remaining data first is deemed lexicographically later). The following is an example of a conflict: MyPrinter.local. A 169.254.99.200 MyPrinter.local. A 169.254.200.50 In this case, 169.254.200.50 is lexicographically later (the third byte, with value 200, is greater than its counterpart with value 99), so it is deemed the winner. Note that it is vital that the bytes are interpreted as UNSIGNED values in the range 0-255, or the wrong outcome may result. In the example above, if the byte with value 200 had been incorrectly interpreted as a signed eight-bit value, then it would be interpreted as value -56, and the wrong address record would be deemed the winner. 8.2.1. Simultaneous Probe Tiebreaking for Multiple Records When a host is probing for a set of records with the same name, or a message is received containing multiple tiebreaker records answering a given probe question in the Question Section, the host's records and the tiebreaker records from the message are each sorted into order, and then compared pairwise, using the same comparison technique described above, until a difference is found. The records are sorted using the same lexicographical order as described above, that is, if the record classes differ, the record with the lower class number comes first. If the classes are the same but the rrtypes differ, the record with the lower rrtype number comes first. If the class and rrtype match, then the rdata is compared bytewise until a difference is found. For example, in the common case of advertising DNS-SD services with a TXT record and an SRV record, the TXT record comes first (the rrtype value for TXT is 16) and the SRV record comes second (the rrtype value for SRV is 33). When comparing the records, if the first records match perfectly, then the second records are compared, and so on. If either list of records runs out of records before any difference is found, then the list with records remaining is deemed to have won the tiebreak. If both lists run out of records at the same time without any difference being found, then this indicates that two devices are advertising identical sets of records, as is sometimes done for fault tolerance, and there is, in fact, no conflict. 8.3. Announcing The second startup step is that the Multicast DNS responder MUST send an unsolicited Multicast DNS response containing, in the Answer Section, all of its newly registered resource records (both shared records, and unique records that have completed the probing step). If there are too many resource records to fit in a single packet, multiple packets should be used. In the case of shared records (e.g., the PTR records used by DNS- Based Service Discovery [RFC6763]), the records are simply placed as is into the Answer Section of the DNS response. In the case of records that have been verified to be unique in the previous step, they are placed into the Answer Section of the DNS response with the most significant bit of the rrclass set to one. The most significant bit of the rrclass for a record in the Answer Section of a response message is the Multicast DNS cache-flush bit and is discussed in more detail below in Section 10.2, "Announcements to Flush Outdated Cache Entries". The Multicast DNS responder MUST send at least two unsolicited responses, one second apart. To provide increased robustness against packet loss, a responder MAY send up to eight unsolicited responses, provided that the interval between unsolicited responses increases by at least a factor of two with every response sent. A Multicast DNS responder MUST NOT send announcements in the absence of information that its network connectivity may have changed in some relevant way. In particular, a Multicast DNS responder MUST NOT send regular periodic announcements as a matter of course. Whenever a Multicast DNS responder receives any Multicast DNS response (solicited or otherwise) containing a conflicting resource record, the conflict MUST be resolved as described in Section 9, "Conflict Resolution". 8.4. Updating At any time, if the rdata of any of a host's Multicast DNS records changes, the host MUST repeat the Announcing step described above to update neighboring caches. For example, if any of a host's IP addresses change, it MUST re-announce those address records. The host does not need to repeat the Probing step because it has already established unique ownership of that name. In the case of shared records, a host MUST send a "goodbye" announcement with RR TTL zero (see Section 10.1, "Goodbye Packets") for the old rdata, to cause it to be deleted from peer caches, before announcing the new rdata. In the case of unique records, a host SHOULD omit the "goodbye" announcement, since the cache-flush bit on the newly announced records will cause old rdata to be flushed from peer caches anyway. A host may update the contents of any of its records at any time, though a host SHOULD NOT update records more frequently than ten times per minute. Frequent rapid updates impose a burden on the network. If a host has information to disseminate which changes more frequently than ten times per minute, then it may be more appropriate to design a protocol for that specific purpose.
10-18
import numpy as np import pandas as pd import matplotlib.pyplot as plt import seaborn as sns import plotly.graph_objects as go import plotly.express as px from scipy.stats import gaussian_kde import matplotlib.font_manager as fm from matplotlib.colors import LinearSegmentedColormap # 设置中文字体支持 plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS', 'Microsoft YaHei', 'WenQuanYi Micro Hei'] plt.rcParams['axes.unicode_minus'] = False # 设置随机种子,确保结果可复现 np.random.seed(42) # 根据92.02%的高准确率生成模拟数据 # 总样本数 n_samples = 1000 # 正确样本比例 (92.02%) correct_ratio = 0.9202 n_correct = int(n_samples * correct_ratio) n_incorrect = n_samples - n_correct # 生成预测不确定性数据 # 正确样本的不确定性较低,分布更集中 correct_uncertainty = np.random.normal(0.3, 0.15, n_correct) # 错误样本的不确定性较高,分布更分散 incorrect_uncertainty = np.random.normal(1.2, 0.4, n_incorrect) # 合并数据 uncertainty = np.concatenate([correct_uncertainty, incorrect_uncertainty]) correctness = np.concatenate([np.ones(n_correct), np.zeros(n_incorrect)]) # 添加峰高变异系数作为第三维度特征 cv_correct = np.random.normal(0.2, 0.1, n_correct) # 正确样本峰高变异系数较低 cv_incorrect = np.random.normal(0.6, 0.2, n_incorrect) # 错误样本峰高变异系数较高 cv = np.concatenate([cv_correct, cv_incorrect]) # 创建数据框 df = pd.DataFrame({ 'uncertainty': uncertainty, 'correctness': correctness, 'result': ['正确' if c == 1 else '错误' for c in correctness], 'cv': cv }) # 确保不确定性值为非负数 df['uncertainty'] = df['uncertainty'].clip(lower=0) # 计算整体准确率 overall_accuracy = df['correctness'].mean() print(f"模拟数据准确率: {overall_accuracy:.4f}") # 创建自定义颜色映射 def create_green_cmap(): colors = ["#f0f9e8", "#bae4bc", "#7bccc4", "#2b8cbe"] return LinearSegmentedColormap.from_list("green_cmap", colors) # 保存所有图像的函数 def save_all_figures(): # 方案1:核密度估计(KDE)+ 统计摘要图 plt.figure(figsize=(12, 8)) kde = gaussian_kde(df['uncertainty']) x_range = np.linspace(0, df['uncertainty'].max(), 200) y_kde = kde(x_range) # 计算统计指标 mean_uncert = df['uncertainty'].mean() median_uncert = df['uncertainty'].median() q25, q75 = np.percentile(df['uncertainty'], [25, 75]) std_uncert = df['uncertainty'].std() plt.plot(x_range, y_kde, 'b-', linewidth=2, label='KDE分布') plt.fill_between(x_range, y_kde, color='royalblue', alpha=0.2, label='分布区域') # 标注统计指标 plt.axvline(mean_uncert, color='r', linestyle='--', label=f'均值: {mean_uncert:.2f}') plt.axvline(median_uncert, color='g', linestyle=':', label=f'中位数: {median_uncert:.2f}') plt.axvline(q25, color='purple', linestyle='-.', label=f'25%分位数: {q25:.2f}') plt.axvline(q75, color='orange', linestyle='-.', label=f'75%分位数: {q75:.2f}') plt.title(f'预测不确定性分布 (准确率: {overall_accuracy * 100:.2f}%)', fontsize=16, pad=20) plt.xlabel('预测方差', fontsize=14) plt.ylabel('概率密度', fontsize=14) plt.legend(loc='upper right', fontsize=12) plt.grid(alpha=0.2, linestyle='--') # 添加统计信息框 stats_text = f'统计摘要:\n样本数: {n_samples}\n标准差: {std_uncert:.2f}\n最小值: {df["uncertainty"].min():.2f}\n最大值: {df["uncertainty"].max():.2f}' plt.text(0.95, 0.95, stats_text, transform=plt.gca().transAxes, fontsize=12, verticalalignment='top', horizontalalignment='right', bbox=dict(boxstyle='round', facecolor='white', alpha=0.8)) plt.tight_layout() plt.savefig('1_kde_distribution.png', dpi=300, bbox_inches='tight') plt.close() # 方案2:分组小提琴图 + 抖动散点图 plt.figure(figsize=(12, 8)) sns.set_style("whitegrid") # 创建自定义调色板 palette = {"正确": "#4caf50", "错误": "#f44336"} # 绘制小提琴图 sns.violinplot(x='result', y='uncertainty', data=df, palette=palette, inner='quartile', linewidth=2, saturation=0.8) # 绘制散点图(带透明度) sns.stripplot(x='result', y='uncertainty', data=df, palette=palette, alpha=0.4, size=4, jitter=0.2) # 添加中位数线 medians = df.groupby('result')['uncertainty'].median() for i, category in enumerate(medians.index): plt.hlines(medians[category], i - 0.3, i + 0.3, color='black', linestyles='dashed', linewidth=2) plt.title(f'预测不确定性与结果分类 (准确率: {overall_accuracy * 100:.2f}%)', fontsize=16, pad=15) plt.xlabel('预测结果', fontsize=14) plt.ylabel('预测方差', fontsize=14) plt.xticks(fontsize=12) plt.yticks(fontsize=12) # 添加准确率注释 for i, category in enumerate(['正确', '错误']): count = len(df[df['result'] == category]) percentage = count / len(df) * 100 plt.text(i, df['uncertainty'].max() + 0.1, f'{count}个样本 ({percentage:.1f}%)', ha='center', fontsize=12) plt.ylim(-0.1, df['uncertainty'].max() + 0.3) plt.tight_layout() plt.savefig('2_violin_scatter.png', dpi=300, bbox_inches='tight') plt.close() # 方案3:热力图(分箱统计正确率) bins = np.linspace(0, df['uncertainty'].max(), 11) df['bin'] = pd.cut(df['uncertainty'], bins=bins, include_lowest=True, labels=False) bin_stats = df.groupby(['bin', 'result']).size().unstack(fill_value=0) bin_stats['accuracy'] = bin_stats['正确'] / bin_stats.sum(axis=1) bin_stats['total_samples'] = bin_stats.sum(axis=1) # 创建热力图数据 heatmap_data = bin_stats['accuracy'].values.reshape(-1, 1) bin_labels = [f'{bins[i]:.2f}-{bins[i + 1]:.2f}' for i in range(len(bins) - 1)] # 使用自定义绿色渐变颜色映射 cmap = create_green_cmap() plt.figure(figsize=(12, 8)) plt.imshow(heatmap_data, cmap=cmap, aspect='auto', vmin=0, vmax=1) # 添加颜色条 cbar = plt.colorbar() cbar.set_label('正确率', fontsize=14) # 添加单元格注释 for i in range(len(bin_labels)): acc = heatmap_data[i, 0] samples = bin_stats['total_samples'].iloc[i] text_color = 'white' if acc < 0.6 else 'black' plt.text(0, i, f'{acc:.2%}\n({samples}样本)', ha='center', va='center', color=text_color, fontsize=11, fontweight='bold') # 设置坐标轴 plt.yticks(range(len(bin_labels)), bin_labels, fontsize=12) plt.xticks([]) plt.ylabel('方差区间', fontsize=14) plt.title(f'不同方差区间的预测正确率 (总体准确率: {overall_accuracy * 100:.2f}%)', fontsize=16, pad=20) # 添加网格线 plt.grid(False) for i in range(len(bin_labels) + 1): plt.axhline(i - 0.5, color='white', linewidth=1) plt.tight_layout() plt.savefig('3_heatmap.png', dpi=300, bbox_inches='tight') plt.close() # 方案4:动态箱线图 + 错误率趋势线 fig = go.Figure() # 添加箱线图 fig.add_trace(go.Box( y=df['uncertainty'], name='方差分布', boxpoints='outliers', marker=dict(color='#2196f3'), line=dict(color='#0d47a1'), fillcolor='rgba(33, 150, 243, 0.5)' )) # 错误率趋势线 df['error'] = 1 - df['correctness'] x_fit = np.linspace(0, df['uncertainty'].max(), 100) z = np.polyfit(df['uncertainty'], df['error'], 3) p = np.poly1d(z) y_fit = p(x_fit) fig.add_trace(go.Scatter( x=x_fit, y=y_fit, name='错误率趋势', mode='lines', line=dict(color='#e53935', width=3), yaxis='y2' )) fig.update_layout( title=dict( text=f'预测方差分布与错误率趋势 (准确率: {overall_accuracy * 100:.2f}%)', font=dict(size=20), ), xaxis=dict(title='预测方差', gridcolor='lightgray'), yaxis=dict( title='方差值', titlefont=dict(color='#2196f3'), tickfont=dict(color='#2196f3'), gridcolor='rgba(33, 150, 243, 0.1)' ), yaxis2=dict( title='错误率', titlefont=dict(color='#e53935'), tickfont=dict(color='#e53935'), overlaying='y', side='right', range=[0, 1] ), template='plotly_white', width=1000, height=700, margin=dict(l=50, r=50, b=80, t=100), legend=dict( orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1 ), hovermode="x unified" ) # 添加注释 fig.add_annotation( x=0.95, y=0.95, xref="paper", yref="paper", text=f"高方差区域错误率显著增加", showarrow=False, font=dict(size=14, color="#e53935"), bgcolor="rgba(255, 255, 255, 0.8)" ) fig.write_image('4_box_trend.png', scale=3) # 方案5:三维密度图 fig = go.Figure() # 添加正确样本 fig.add_trace(go.Scatter3d( x=df[df['result'] == '正确']['uncertainty'], y=df[df['result'] == '正确']['cv'], z=df[df['result'] == '正确']['correctness'], mode='markers', name='正确', marker=dict( size=5, color='#4caf50', opacity=0.7 ) )) # 添加错误样本 fig.add_trace(go.Scatter3d( x=df[df['result'] == '错误']['uncertainty'], y=df[df['result'] == '错误']['cv'], z=df[df['result'] == '错误']['correctness'], mode='markers', name='错误', marker=dict( size=7, color='#f44336', opacity=0.8, symbol='diamond' ) )) fig.update_layout( title=dict( text=f'三维预测不确定性分析 (准确率: {overall_accuracy * 100:.2f}%)', font=dict(size=20), y=0.95 ), scene=dict( xaxis_title='预测方差', yaxis_title='峰高变异系数', zaxis_title='预测正确(1)/错误(0)', camera=dict( eye=dict(x=1.5, y=1.5, z=0.8) ) ), width=1000, height=800, margin=dict(l=0, r=0, b=0, t=50), legend=dict( yanchor="top", y=0.99, xanchor="left", x=0.01 ) ) # 添加分类平面 fig.add_trace(go.Mesh3d( x=[0, 2, 2, 0], y=[0, 0, 1, 1], z=[0.5, 0.5, 0.5, 0.5], opacity=0.2, color='gray', name='分类平面' )) fig.write_image('5_3d_density.png', scale=3) print("所有图像已保存为PNG文件") # 生成并保存所有图像 save_all_figures() 这个代码显示不出中文字体 C:\python\py\.venv\Scripts\python.exe C:\python\py\3.py 模拟数据准确率: 0.9200 C:\python\py\3.py:113: FutureWarning: Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `x` variable to `hue` and set `legend=False` for the same effect. C:\python\py\3.py:117: FutureWarning: Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `x` variable to `hue` and set `legend=False` for the same effect. C:\python\py\3.py:140: UserWarning: Glyph 27491 (\N{CJK UNIFIED IDEOGRAPH-6B63}) missing from font(s) Arial. C:\python\py\3.py:140: UserWarning: Glyph 30830 (\N{CJK UNIFIED IDEOGRAPH-786E}) missing from font(s) Arial. C:\python\py\3.py:140: UserWarning: Glyph 38169 (\N{CJK UNIFIED IDEOGRAPH-9519}) missing from font(s) Arial. C:\python\py\3.py:140: UserWarning: Glyph 35823 (\N{CJK UNIFIED IDEOGRAPH-8BEF}) missing from font(s) Arial. C:\python\py\3.py:140: UserWarning: Glyph 39044 (\N{CJK UNIFIED IDEOGRAPH-9884}) missing from font(s) Arial. C:\python\py\3.py:140: UserWarning: Glyph 27979 (\N{CJK UNIFIED IDEOGRAPH-6D4B}) missing from font(s) Arial. C:\python\py\3.py:140: UserWarning: Glyph 32467 (\N{CJK UNIFIED IDEOGRAPH-7ED3}) missing from font(s) Arial. C:\python\py\3.py:140: UserWarning: Glyph 26524 (\N{CJK UNIFIED IDEOGRAPH-679C}) missing from font(s) Arial. C:\python\py\3.py:140: UserWarning: Glyph 26041 (\N{CJK UNIFIED IDEOGRAPH-65B9}) missing from font(s) Arial. C:\python\py\3.py:140: UserWarning: Glyph 24046 (\N{CJK UNIFIED IDEOGRAPH-5DEE}) missing from font(s) Arial. C:\python\py\3.py:140: UserWarning: Glyph 19981 (\N{CJK UNIFIED IDEOGRAPH-4E0D}) missing from font(s) Arial. C:\python\py\3.py:140: UserWarning: Glyph 23450 (\N{CJK UNIFIED IDEOGRAPH-5B9A}) missing from font(s) Arial. C:\python\py\3.py:140: UserWarning: Glyph 24615 (\N{CJK UNIFIED IDEOGRAPH-6027}) missing from font(s) Arial. C:\python\py\3.py:140: UserWarning: Glyph 19982 (\N{CJK UNIFIED IDEOGRAPH-4E0E}) missing from font(s) Arial. C:\python\py\3.py:140: UserWarning: Glyph 20998 (\N{CJK UNIFIED IDEOGRAPH-5206}) missing from font(s) Arial. C:\python\py\3.py:140: UserWarning: Glyph 31867 (\N{CJK UNIFIED IDEOGRAPH-7C7B}) missing from font(s) Arial. C:\python\py\3.py:140: UserWarning: Glyph 20934 (\N{CJK UNIFIED IDEOGRAPH-51C6}) missing from font(s) Arial. C:\python\py\3.py:140: UserWarning: Glyph 29575 (\N{CJK UNIFIED IDEOGRAPH-7387}) missing from font(s) Arial. C:\python\py\3.py:140: UserWarning: Glyph 20010 (\N{CJK UNIFIED IDEOGRAPH-4E2A}) missing from font(s) Arial. C:\python\py\3.py:140: UserWarning: Glyph 26679 (\N{CJK UNIFIED IDEOGRAPH-6837}) missing from font(s) Arial. C:\python\py\3.py:140: UserWarning: Glyph 26412 (\N{CJK UNIFIED IDEOGRAPH-672C}) missing from font(s) Arial. C:\python\py\3.py:141: UserWarning: Glyph 27491 (\N{CJK UNIFIED IDEOGRAPH-6B63}) missing from font(s) Arial. C:\python\py\3.py:141: UserWarning: Glyph 30830 (\N{CJK UNIFIED IDEOGRAPH-786E}) missing from font(s) Arial. C:\python\py\3.py:141: UserWarning: Glyph 38169 (\N{CJK UNIFIED IDEOGRAPH-9519}) missing from font(s) Arial. C:\python\py\3.py:141: UserWarning: Glyph 35823 (\N{CJK UNIFIED IDEOGRAPH-8BEF}) missing from font(s) Arial. C:\python\py\3.py:141: UserWarning: Glyph 39044 (\N{CJK UNIFIED IDEOGRAPH-9884}) missing from font(s) Arial. C:\python\py\3.py:141: UserWarning: Glyph 27979 (\N{CJK UNIFIED IDEOGRAPH-6D4B}) missing from font(s) Arial. C:\python\py\3.py:141: UserWarning: Glyph 32467 (\N{CJK UNIFIED IDEOGRAPH-7ED3}) missing from font(s) Arial. C:\python\py\3.py:141: UserWarning: Glyph 26524 (\N{CJK UNIFIED IDEOGRAPH-679C}) missing from font(s) Arial. C:\python\py\3.py:141: UserWarning: Glyph 26041 (\N{CJK UNIFIED IDEOGRAPH-65B9}) missing from font(s) Arial. C:\python\py\3.py:141: UserWarning: Glyph 24046 (\N{CJK UNIFIED IDEOGRAPH-5DEE}) missing from font(s) Arial. C:\python\py\3.py:141: UserWarning: Glyph 19981 (\N{CJK UNIFIED IDEOGRAPH-4E0D}) missing from font(s) Arial. C:\python\py\3.py:141: UserWarning: Glyph 23450 (\N{CJK UNIFIED IDEOGRAPH-5B9A}) missing from font(s) Arial. C:\python\py\3.py:141: UserWarning: Glyph 24615 (\N{CJK UNIFIED IDEOGRAPH-6027}) missing from font(s) Arial. C:\python\py\3.py:141: UserWarning: Glyph 19982 (\N{CJK UNIFIED IDEOGRAPH-4E0E}) missing from font(s) Arial. C:\python\py\3.py:141: UserWarning: Glyph 20998 (\N{CJK UNIFIED IDEOGRAPH-5206}) missing from font(s) Arial. C:\python\py\3.py:141: UserWarning: Glyph 31867 (\N{CJK UNIFIED IDEOGRAPH-7C7B}) missing from font(s) Arial. C:\python\py\3.py:141: UserWarning: Glyph 20934 (\N{CJK UNIFIED IDEOGRAPH-51C6}) missing from font(s) Arial. C:\python\py\3.py:141: UserWarning: Glyph 29575 (\N{CJK UNIFIED IDEOGRAPH-7387}) missing from font(s) Arial. C:\python\py\3.py:141: UserWarning: Glyph 20010 (\N{CJK UNIFIED IDEOGRAPH-4E2A}) missing from font(s) Arial. C:\python\py\3.py:141: UserWarning: Glyph 26679 (\N{CJK UNIFIED IDEOGRAPH-6837}) missing from font(s) Arial. C:\python\py\3.py:141: UserWarning: Glyph 26412 (\N{CJK UNIFIED IDEOGRAPH-672C}) missing from font(s) Arial. C:\python\PyCharm 2024.3.5\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:124: UserWarning: Glyph 27491 (\N{CJK UNIFIED IDEOGRAPH-6B63}) missing from font(s) Arial. C:\python\PyCharm 2024.3.5\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:124: UserWarning: Glyph 30830 (\N{CJK UNIFIED IDEOGRAPH-786E}) missing from font(s) Arial. C:\python\PyCharm 2024.3.5\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:124: UserWarning: Glyph 38169 (\N{CJK UNIFIED IDEOGRAPH-9519}) missing from font(s) Arial. C:\python\PyCharm 2024.3.5\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:124: UserWarning: Glyph 35823 (\N{CJK UNIFIED IDEOGRAPH-8BEF}) missing from font(s) Arial. C:\python\PyCharm 2024.3.5\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:124: UserWarning: Glyph 39044 (\N{CJK UNIFIED IDEOGRAPH-9884}) missing from font(s) Arial. C:\python\PyCharm 2024.3.5\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:124: UserWarning: Glyph 27979 (\N{CJK UNIFIED IDEOGRAPH-6D4B}) missing from font(s) Arial. C:\python\PyCharm 2024.3.5\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:124: UserWarning: Glyph 32467 (\N{CJK UNIFIED IDEOGRAPH-7ED3}) missing from font(s) Arial. C:\python\PyCharm 2024.3.5\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:124: UserWarning: Glyph 26524 (\N{CJK UNIFIED IDEOGRAPH-679C}) missing from font(s) Arial. C:\python\PyCharm 2024.3.5\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:124: UserWarning: Glyph 26041 (\N{CJK UNIFIED IDEOGRAPH-65B9}) missing from font(s) Arial. C:\python\PyCharm 2024.3.5\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:124: UserWarning: Glyph 24046 (\N{CJK UNIFIED IDEOGRAPH-5DEE}) missing from font(s) Arial. C:\python\PyCharm 2024.3.5\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:124: UserWarning: Glyph 19981 (\N{CJK UNIFIED IDEOGRAPH-4E0D}) missing from font(s) Arial. C:\python\PyCharm 2024.3.5\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:124: UserWarning: Glyph 23450 (\N{CJK UNIFIED IDEOGRAPH-5B9A}) missing from font(s) Arial. C:\python\PyCharm 2024.3.5\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:124: UserWarning: Glyph 24615 (\N{CJK UNIFIED IDEOGRAPH-6027}) missing from font(s) Arial. C:\python\PyCharm 2024.3.5\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:124: UserWarning: Glyph 19982 (\N{CJK UNIFIED IDEOGRAPH-4E0E}) missing from font(s) Arial. C:\python\PyCharm 2024.3.5\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:124: UserWarning: Glyph 20998 (\N{CJK UNIFIED IDEOGRAPH-5206}) missing from font(s) Arial. C:\python\PyCharm 2024.3.5\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:124: UserWarning: Glyph 31867 (\N{CJK UNIFIED IDEOGRAPH-7C7B}) missing from font(s) Arial. C:\python\PyCharm 2024.3.5\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:124: UserWarning: Glyph 20934 (\N{CJK UNIFIED IDEOGRAPH-51C6}) missing from font(s) Arial. C:\python\PyCharm 2024.3.5\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:124: UserWarning: Glyph 29575 (\N{CJK UNIFIED IDEOGRAPH-7387}) missing from font(s) Arial. C:\python\PyCharm 2024.3.5\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:124: UserWarning: Glyph 20010 (\N{CJK UNIFIED IDEOGRAPH-4E2A}) missing from font(s) Arial. C:\python\PyCharm 2024.3.5\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:124: UserWarning: Glyph 26679 (\N{CJK UNIFIED IDEOGRAPH-6837}) missing from font(s) Arial. C:\python\PyCharm 2024.3.5\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:124: UserWarning: Glyph 26412 (\N{CJK UNIFIED IDEOGRAPH-672C}) missing from font(s) Arial. C:\python\py\3.py:185: UserWarning: Glyph 26041 (\N{CJK UNIFIED IDEOGRAPH-65B9}) missing from font(s) Arial. C:\python\py\3.py:185: UserWarning: Glyph 24046 (\N{CJK UNIFIED IDEOGRAPH-5DEE}) missing from font(s) Arial. C:\python\py\3.py:185: UserWarning: Glyph 21306 (\N{CJK UNIFIED IDEOGRAPH-533A}) missing from font(s) Arial. C:\python\py\3.py:185: UserWarning: Glyph 38388 (\N{CJK UNIFIED IDEOGRAPH-95F4}) missing from font(s) Arial. C:\python\py\3.py:185: UserWarning: Glyph 19981 (\N{CJK UNIFIED IDEOGRAPH-4E0D}) missing from font(s) Arial. C:\python\py\3.py:185: UserWarning: Glyph 21516 (\N{CJK UNIFIED IDEOGRAPH-540C}) missing from font(s) Arial. C:\python\py\3.py:185: UserWarning: Glyph 30340 (\N{CJK UNIFIED IDEOGRAPH-7684}) missing from font(s) Arial. C:\python\py\3.py:185: UserWarning: Glyph 39044 (\N{CJK UNIFIED IDEOGRAPH-9884}) missing from font(s) Arial. C:\python\py\3.py:185: UserWarning: Glyph 27979 (\N{CJK UNIFIED IDEOGRAPH-6D4B}) missing from font(s) Arial. C:\python\py\3.py:185: UserWarning: Glyph 27491 (\N{CJK UNIFIED IDEOGRAPH-6B63}) missing from font(s) Arial. C:\python\py\3.py:185: UserWarning: Glyph 30830 (\N{CJK UNIFIED IDEOGRAPH-786E}) missing from font(s) Arial. C:\python\py\3.py:185: UserWarning: Glyph 29575 (\N{CJK UNIFIED IDEOGRAPH-7387}) missing from font(s) Arial. C:\python\py\3.py:185: UserWarning: Glyph 24635 (\N{CJK UNIFIED IDEOGRAPH-603B}) missing from font(s) Arial. C:\python\py\3.py:185: UserWarning: Glyph 20307 (\N{CJK UNIFIED IDEOGRAPH-4F53}) missing from font(s) Arial. C:\python\py\3.py:185: UserWarning: Glyph 20934 (\N{CJK UNIFIED IDEOGRAPH-51C6}) missing from font(s) Arial. C:\python\py\3.py:185: UserWarning: Glyph 26679 (\N{CJK UNIFIED IDEOGRAPH-6837}) missing from font(s) Arial. C:\python\py\3.py:185: UserWarning: Glyph 26412 (\N{CJK UNIFIED IDEOGRAPH-672C}) missing from font(s) Arial. C:\python\py\3.py:186: UserWarning: Glyph 26041 (\N{CJK UNIFIED IDEOGRAPH-65B9}) missing from font(s) Arial. C:\python\py\3.py:186: UserWarning: Glyph 24046 (\N{CJK UNIFIED IDEOGRAPH-5DEE}) missing from font(s) Arial. C:\python\py\3.py:186: UserWarning: Glyph 21306 (\N{CJK UNIFIED IDEOGRAPH-533A}) missing from font(s) Arial. C:\python\py\3.py:186: UserWarning: Glyph 38388 (\N{CJK UNIFIED IDEOGRAPH-95F4}) missing from font(s) Arial. C:\python\py\3.py:186: UserWarning: Glyph 19981 (\N{CJK UNIFIED IDEOGRAPH-4E0D}) missing from font(s) Arial. C:\python\py\3.py:186: UserWarning: Glyph 21516 (\N{CJK UNIFIED IDEOGRAPH-540C}) missing from font(s) Arial. C:\python\py\3.py:186: UserWarning: Glyph 30340 (\N{CJK UNIFIED IDEOGRAPH-7684}) missing from font(s) Arial. C:\python\py\3.py:186: UserWarning: Glyph 39044 (\N{CJK UNIFIED IDEOGRAPH-9884}) missing from font(s) Arial. C:\python\py\3.py:186: UserWarning: Glyph 27979 (\N{CJK UNIFIED IDEOGRAPH-6D4B}) missing from font(s) Arial. C:\python\py\3.py:186: UserWarning: Glyph 27491 (\N{CJK UNIFIED IDEOGRAPH-6B63}) missing from font(s) Arial. C:\python\py\3.py:186: UserWarning: Glyph 30830 (\N{CJK UNIFIED IDEOGRAPH-786E}) missing from font(s) Arial. C:\python\py\3.py:186: UserWarning: Glyph 29575 (\N{CJK UNIFIED IDEOGRAPH-7387}) missing from font(s) Arial. C:\python\py\3.py:186: UserWarning: Glyph 24635 (\N{CJK UNIFIED IDEOGRAPH-603B}) missing from font(s) Arial. C:\python\py\3.py:186: UserWarning: Glyph 20307 (\N{CJK UNIFIED IDEOGRAPH-4F53}) missing from font(s) Arial. C:\python\py\3.py:186: UserWarning: Glyph 20934 (\N{CJK UNIFIED IDEOGRAPH-51C6}) missing from font(s) Arial. C:\python\py\3.py:186: UserWarning: Glyph 26679 (\N{CJK UNIFIED IDEOGRAPH-6837}) missing from font(s) Arial. C:\python\py\3.py:186: UserWarning: Glyph 26412 (\N{CJK UNIFIED IDEOGRAPH-672C}) missing from font(s) Arial. C:\python\PyCharm 2024.3.5\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:124: UserWarning: Glyph 26041 (\N{CJK UNIFIED IDEOGRAPH-65B9}) missing from font(s) Arial. C:\python\PyCharm 2024.3.5\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:124: UserWarning: Glyph 24046 (\N{CJK UNIFIED IDEOGRAPH-5DEE}) missing from font(s) Arial. C:\python\PyCharm 2024.3.5\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:124: UserWarning: Glyph 21306 (\N{CJK UNIFIED IDEOGRAPH-533A}) missing from font(s) Arial. C:\python\PyCharm 2024.3.5\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:124: UserWarning: Glyph 38388 (\N{CJK UNIFIED IDEOGRAPH-95F4}) missing from font(s) Arial. C:\python\PyCharm 2024.3.5\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:124: UserWarning: Glyph 19981 (\N{CJK UNIFIED IDEOGRAPH-4E0D}) missing from font(s) Arial. C:\python\PyCharm 2024.3.5\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:124: UserWarning: Glyph 21516 (\N{CJK UNIFIED IDEOGRAPH-540C}) missing from font(s) Arial. C:\python\PyCharm 2024.3.5\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:124: UserWarning: Glyph 30340 (\N{CJK UNIFIED IDEOGRAPH-7684}) missing from font(s) Arial. C:\python\PyCharm 2024.3.5\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:124: UserWarning: Glyph 39044 (\N{CJK UNIFIED IDEOGRAPH-9884}) missing from font(s) Arial. C:\python\PyCharm 2024.3.5\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:124: UserWarning: Glyph 27979 (\N{CJK UNIFIED IDEOGRAPH-6D4B}) missing from font(s) Arial. C:\python\PyCharm 2024.3.5\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:124: UserWarning: Glyph 27491 (\N{CJK UNIFIED IDEOGRAPH-6B63}) missing from font(s) Arial. C:\python\PyCharm 2024.3.5\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:124: UserWarning: Glyph 30830 (\N{CJK UNIFIED IDEOGRAPH-786E}) missing from font(s) Arial. C:\python\PyCharm 2024.3.5\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:124: UserWarning: Glyph 29575 (\N{CJK UNIFIED IDEOGRAPH-7387}) missing from font(s) Arial. C:\python\PyCharm 2024.3.5\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:124: UserWarning: Glyph 24635 (\N{CJK UNIFIED IDEOGRAPH-603B}) missing from font(s) Arial. C:\python\PyCharm 2024.3.5\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:124: UserWarning: Glyph 20307 (\N{CJK UNIFIED IDEOGRAPH-4F53}) missing from font(s) Arial. C:\python\PyCharm 2024.3.5\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:124: UserWarning: Glyph 20934 (\N{CJK UNIFIED IDEOGRAPH-51C6}) missing from font(s) Arial. C:\python\PyCharm 2024.3.5\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:124: UserWarning: Glyph 26679 (\N{CJK UNIFIED IDEOGRAPH-6837}) missing from font(s) Arial. C:\python\PyCharm 2024.3.5\plugins\python-ce\helpers\pycharm_matplotlib_backend\backend_interagg.py:124: UserWarning: Glyph 26412 (\N{CJK UNIFIED IDEOGRAPH-672C}) missing from font(s) Arial. Traceback (most recent call last): File "C:\python\py\3.py", line 339, in <module> save_all_figures() ~~~~~~~~~~~~~~~~^^ File "C:\python\py\3.py", line 218, in save_all_figures fig.update_layout( ~~~~~~~~~~~~~~~~~^ title=dict( ^^^^^^^^^^^ ...<29 lines>... hovermode="x unified" ^^^^^^^^^^^^^^^^^^^^^ ) ^ File "C:\python\py\.venv\Lib\site-packages\plotly\graph_objs\_figure.py", line 218, in update_layout return super().update_layout(dict1, overwrite, **kwargs) ~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\python\py\.venv\Lib\site-packages\plotly\basedatatypes.py", line 1415, in update_layout self.layout.update(dict1, overwrite=overwrite, **kwargs) ~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\python\py\.venv\Lib\site-packages\plotly\basedatatypes.py", line 5195, in update BaseFigure._perform_update(self, kwargs, overwrite=overwrite) ~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\python\py\.venv\Lib\site-packages\plotly\basedatatypes.py", line 3971, in _perform_update BaseFigure._perform_update(plotly_obj[key], val) ~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^ File "C:\python\py\.venv\Lib\site-packages\plotly\basedatatypes.py", line 3949, in _perform_update raise err ValueError: Invalid property specified for object of type plotly.graph_objs.layout.YAxis: 'titlefont' Did you mean "tickfont"? Valid properties: anchor If set to an opposite-letter axis id (e.g. `x2`, `y`), this axis is bound to the corresponding opposite-letter axis. If set to "free", this axis' position is determined by `position`. automargin Determines whether long tick labels automatically grow the figure margins. autorange Determines whether or not the range of this axis is computed in relation to the input data. See `rangemode` for more info. If `range` is provided and it has a value for both the lower and upper bound, `autorange` is set to False. Using "min" applies autorange only to set the minimum. Using "max" applies autorange only to set the maximum. Using *min reversed* applies autorange only to set the minimum on a reversed axis. Using *max reversed* applies autorange only to set the maximum on a reversed axis. Using "reversed" applies autorange on both ends and reverses the axis direction. autorangeoptions :class:`plotly.graph_objects.layout.yaxis.Autorangeopti ons` instance or dict with compatible properties autoshift Automatically reposition the axis to avoid overlap with other axes with the same `overlaying` value. This repositioning will account for any `shift` amount applied to other axes on the same side with `autoshift` is set to true. Only has an effect if `anchor` is set to "free". autotickangles When `tickangle` is set to "auto", it will be set to the first angle in this array that is large enough to prevent label overlap. autotypenumbers Using "strict" a numeric string in trace data is not converted to a number. Using *convert types* a numeric string in trace data may be treated as a number during automatic axis `type` detection. Defaults to layout.autotypenumbers. calendar Sets the calendar system to use for `range` and `tick0` if this is a date axis. This does not set the calendar for interpreting data on this axis, that's specified in the trace or via the global `layout.calendar` categoryarray Sets the order in which categories on this axis appear. Only has an effect if `categoryorder` is set to "array". Used with `categoryorder`. categoryarraysrc Sets the source reference on Chart Studio Cloud for `categoryarray`. categoryorder Specifies the ordering logic for the case of categorical variables. By default, plotly uses "trace", which specifies the order that is present in the data supplied. Set `categoryorder` to *category ascending* or *category descending* if order should be determined by the alphanumerical order of the category names. Set `categoryorder` to "array" to derive the ordering from the attribute `categoryarray`. If a category is not found in the `categoryarray` array, the sorting behavior for that attribute will be identical to the "trace" mode. The unspecified categories will follow the categories in `categoryarray`. Set `categoryorder` to *total ascending* or *total descending* if order should be determined by the numerical order of the values. Similarly, the order can be determined by the min, max, sum, mean, geometric mean or median of all the values. color Sets default for all colors associated with this axis all at once: line, font, tick, and grid colors. Grid color is lightened by blending this with the plot background Individual pieces can override this. constrain If this axis needs to be compressed (either due to its own `scaleanchor` and `scaleratio` or those of the other axis), determines how that happens: by increasing the "range", or by decreasing the "domain". Default is "domain" for axes containing image traces, "range" otherwise. constraintoward If this axis needs to be compressed (either due to its own `scaleanchor` and `scaleratio` or those of the other axis), determines which direction we push the originally specified plot area. Options are "left", "center" (default), and "right" for x axes, and "top", "middle" (default), and "bottom" for y axes. dividercolor Sets the color of the dividers Only has an effect on "multicategory" axes. dividerwidth Sets the width (in px) of the dividers Only has an effect on "multicategory" axes. domain Sets the domain of this axis (in plot fraction). dtick Sets the step in-between ticks on this axis. Use with `tick0`. Must be a positive number, or special strings available to "log" and "date" axes. If the axis `type` is "log", then ticks are set every 10^(n*dtick) where n is the tick number. For example, to set a tick mark at 1, 10, 100, 1000, ... set dtick to 1. To set tick marks at 1, 100, 10000, ... set dtick to 2. To set tick marks at 1, 5, 25, 125, 625, 3125, ... set dtick to log_10(5), or 0.69897000433. "log" has several special values; "L<f>", where `f` is a positive number, gives ticks linearly spaced in value (but not position). For example `tick0` = 0.1, `dtick` = "L0.5" will put ticks at 0.1, 0.6, 1.1, 1.6 etc. To show powers of 10 plus small digits between, use "D1" (all digits) or "D2" (only 2 and 5). `tick0` is ignored for "D1" and "D2". If the axis `type` is "date", then you must convert the time to milliseconds. For example, to set the interval between ticks to one day, set `dtick` to 86400000.0. "date" also has special values "M<n>" gives ticks spaced by a number of months. `n` must be a positive integer. To set ticks on the 15th of every third month, set `tick0` to "2000-01-15" and `dtick` to "M3". To set ticks every 4 years, set `dtick` to "M48" exponentformat Determines a formatting rule for the tick exponents. For example, consider the number 1,000,000,000. If "none", it appears as 1,000,000,000. If "e", 1e+9. If "E", 1E+9. If "power", 1x10^9 (with 9 in a super script). If "SI", 1G. If "B", 1B. fixedrange Determines whether or not this axis is zoom-able. If true, then zoom is disabled. gridcolor Sets the color of the grid lines. griddash Sets the dash style of lines. Set to a dash type string ("solid", "dot", "dash", "longdash", "dashdot", or "longdashdot") or a dash length list in px (eg "5px,10px,2px,2px"). gridwidth Sets the width (in px) of the grid lines. hoverformat Sets the hover text formatting rule using d3 formatting mini-languages which are very similar to those in Python. For numbers, see: https://github.com/d3/d3-format/tree/v1.4.5#d3-format. And for dates see: https://github.com/d3/d3-time- format/tree/v2.2.3#locale_format. We add two items to d3's date formatter: "%h" for half of the year as a decimal number as well as "%{n}f" for fractional seconds with n digits. For example, *2016-10-13 09:15:23.456* with tickformat "%H~%M~%S.%2f" would display "09~15~23.46" insiderange Could be used to set the desired inside range of this axis (excluding the labels) when `ticklabelposition` of the anchored axis has "inside". Not implemented for axes with `type` "log". This would be ignored when `range` is provided. labelalias Replacement text for specific tick or hover labels. For example using {US: 'USA', CA: 'Canada'} changes US to USA and CA to Canada. The labels we would have shown must match the keys exactly, after adding any tickprefix or ticksuffix. For negative numbers the minus sign symbol used (U+2212) is wider than the regular ascii dash. That means you need to use −1 instead of -1. labelalias can be used with any axis type, and both keys (if needed) and values (if desired) can include html-like tags or MathJax. layer Sets the layer on which this axis is displayed. If *above traces*, this axis is displayed above all the subplot's traces If *below traces*, this axis is displayed below all the subplot's traces, but above the grid lines. Useful when used together with scatter-like traces with `cliponaxis` set to False to show markers and/or text nodes above this axis. linecolor Sets the axis line color. linewidth Sets the width (in px) of the axis line. matches If set to another axis id (e.g. `x2`, `y`), the range of this axis will match the range of the corresponding axis in data-coordinates space. Moreover, matching axes share auto-range values, category lists and histogram auto-bins. Note that setting axes simultaneously in both a `scaleanchor` and a `matches` constraint is currently forbidden. Moreover, note that matching axes must have the same `type`. maxallowed Determines the maximum range of this axis. minallowed Determines the minimum range of this axis. minexponent Hide SI prefix for 10^n if |n| is below this number. This only has an effect when `tickformat` is "SI" or "B". minor :class:`plotly.graph_objects.layout.yaxis.Minor` instance or dict with compatible properties mirror Determines if the axis lines or/and ticks are mirrored to the opposite side of the plotting area. If True, the axis lines are mirrored. If "ticks", the axis lines and ticks are mirrored. If False, mirroring is disable. If "all", axis lines are mirrored on all shared-axes subplots. If "allticks", axis lines and ticks are mirrored on all shared-axes subplots. nticks Specifies the maximum number of ticks for the particular axis. The actual number of ticks will be chosen automatically to be less than or equal to `nticks`. Has an effect only if `tickmode` is set to "auto". overlaying If set a same-letter axis id, this axis is overlaid on top of the corresponding same-letter axis, with traces and axes visible for both axes. If False, this axis does not overlay any same-letter axes. In this case, for axes with overlapping domains only the highest- numbered axis will be visible. position Sets the position of this axis in the plotting space (in normalized coordinates). Only has an effect if `anchor` is set to "free". range Sets the range of this axis. If the axis `type` is "log", then you must take the log of your desired range (e.g. to set the range from 1 to 100, set the range from 0 to 2). If the axis `type` is "date", it should be date strings, like date data, though Date objects and unix milliseconds will be accepted and converted to strings. If the axis `type` is "category", it should be numbers, using the scale where each category is assigned a serial number from zero in the order it appears. Leaving either or both elements `null` impacts the default `autorange`. rangebreaks A tuple of :class:`plotly.graph_objects.layout.yaxis.Rangebreak` instances or dicts with compatible properties rangebreakdefaults When used in a template (as layout.template.layout.yaxis.rangebreakdefaults), sets the default property values to use for elements of layout.yaxis.rangebreaks rangemode If "normal", the range is computed in relation to the extrema of the input data. If "tozero", the range extends to 0, regardless of the input data If "nonnegative", the range is non-negative, regardless of the input data. Applies only to linear axes. scaleanchor If set to another axis id (e.g. `x2`, `y`), the range of this axis changes together with the range of the corresponding axis such that the scale of pixels per unit is in a constant ratio. Both axes are still zoomable, but when you zoom one, the other will zoom the same amount, keeping a fixed midpoint. `constrain` and `constraintoward` determine how we enforce the constraint. You can chain these, ie `yaxis: {scaleanchor: *x*}, xaxis2: {scaleanchor: *y*}` but you can only link axes of the same `type`. The linked axis can have the opposite letter (to constrain the aspect ratio) or the same letter (to match scales across subplots). Loops (`yaxis: {scaleanchor: *x*}, xaxis: {scaleanchor: *y*}` or longer) are redundant and the last constraint encountered will be ignored to avoid possible inconsistent constraints via `scaleratio`. Note that setting axes simultaneously in both a `scaleanchor` and a `matches` constraint is currently forbidden. Setting `false` allows to remove a default constraint (occasionally, you may need to prevent a default `scaleanchor` constraint from being applied, eg. when having an image trace `yaxis: {scaleanchor: "x"}` is set automatically in order for pixels to be rendered as squares, setting `yaxis: {scaleanchor: false}` allows to remove the constraint). scaleratio If this axis is linked to another by `scaleanchor`, this determines the pixel to unit scale ratio. For example, if this value is 10, then every unit on this axis spans 10 times the number of pixels as a unit on the linked axis. Use this for example to create an elevation profile where the vertical scale is exaggerated a fixed amount with respect to the horizontal. separatethousands If "true", even 4-digit integers are separated shift Moves the axis a given number of pixels from where it would have been otherwise. Accepts both positive and negative values, which will shift the axis either right or left, respectively. If `autoshift` is set to true, then this defaults to a padding of -3 if `side` is set to "left". and defaults to +3 if `side` is set to "right". Defaults to 0 if `autoshift` is set to false. Only has an effect if `anchor` is set to "free". showdividers Determines whether or not a dividers are drawn between the category levels of this axis. Only has an effect on "multicategory" axes. showexponent If "all", all exponents are shown besides their significands. If "first", only the exponent of the first tick is shown. If "last", only the exponent of the last tick is shown. If "none", no exponents appear. showgrid Determines whether or not grid lines are drawn. If True, the grid lines are drawn at every tick mark. showline Determines whether or not a line bounding this axis is drawn. showspikes Determines whether or not spikes (aka droplines) are drawn for this axis. Note: This only takes affect when hovermode = closest showticklabels Determines whether or not the tick labels are drawn. showtickprefix If "all", all tick labels are displayed with a prefix. If "first", only the first tick is displayed with a prefix. If "last", only the last tick is displayed with a suffix. If "none", tick prefixes are hidden. showticksuffix Same as `showtickprefix` but for tick suffixes. side Determines whether a x (y) axis is positioned at the "bottom" ("left") or "top" ("right") of the plotting area. spikecolor Sets the spike color. If undefined, will use the series color spikedash Sets the dash style of lines. Set to a dash type string ("solid", "dot", "dash", "longdash", "dashdot", or "longdashdot") or a dash length list in px (eg "5px,10px,2px,2px"). spikemode Determines the drawing mode for the spike line If "toaxis", the line is drawn from the data point to the axis the series is plotted on. If "across", the line is drawn across the entire plot area, and supercedes "toaxis". If "marker", then a marker dot is drawn on the axis the series is plotted on spikesnap Determines whether spikelines are stuck to the cursor or to the closest datapoints. spikethickness Sets the width (in px) of the zero line. tick0 Sets the placement of the first tick on this axis. Use with `dtick`. If the axis `type` is "log", then you must take the log of your starting tick (e.g. to set the starting tick to 100, set the `tick0` to 2) except when `dtick`=*L<f>* (see `dtick` for more info). If the axis `type` is "date", it should be a date string, like date data. If the axis `type` is "category", it should be a number, using the scale where each category is assigned a serial number from zero in the order it appears. tickangle Sets the angle of the tick labels with respect to the horizontal. For example, a `tickangle` of -90 draws the tick labels vertically. tickcolor Sets the tick color. tickfont Sets the tick font. tickformat Sets the tick label formatting rule using d3 formatting mini-languages which are very similar to those in Python. For numbers, see: https://github.com/d3/d3-format/tree/v1.4.5#d3-format. And for dates see: https://github.com/d3/d3-time- format/tree/v2.2.3#locale_format. We add two items to d3's date formatter: "%h" for half of the year as a decimal number as well as "%{n}f" for fractional seconds with n digits. For example, *2016-10-13 09:15:23.456* with tickformat "%H~%M~%S.%2f" would display "09~15~23.46" tickformatstops A tuple of :class:`plotly.graph_objects.layout.yaxis.Ti ckformatstop` instances or dicts with compatible properties tickformatstopdefaults When used in a template (as layout.template.layout.yaxis.tickformatstopdefaults), sets the default property values to use for elements of layout.yaxis.tickformatstops ticklabelindex Only for axes with `type` "date" or "linear". Instead of drawing the major tick label, draw the label for the minor tick that is n positions away from the major tick. E.g. to always draw the label for the minor tick before each major tick, choose `ticklabelindex` -1. This is useful for date axes with `ticklabelmode` "period" if you want to label the period that ends with each major tick instead of the period that begins there. ticklabelindexsrc Sets the source reference on Chart Studio Cloud for `ticklabelindex`. ticklabelmode Determines where tick labels are drawn with respect to their corresponding ticks and grid lines. Only has an effect for axes of `type` "date" When set to "period", tick labels are drawn in the middle of the period between ticks. ticklabeloverflow Determines how we handle tick labels that would overflow either the graph div or the domain of the axis. The default value for inside tick labels is *hide past domain*. Otherwise on "category" and "multicategory" axes the default is "allow". In other cases the default is *hide past div*. ticklabelposition Determines where tick labels are drawn with respect to the axis Please note that top or bottom has no effect on x axes or when `ticklabelmode` is set to "period". Similarly left or right has no effect on y axes or when `ticklabelmode` is set to "period". Has no effect on "multicategory" axes or when `tickson` is set to "boundaries". When used on axes linked by `matches` or `scaleanchor`, no extra padding for inside labels would be added by autorange, so that the scales could match. ticklabelshift Shifts the tick labels by the specified number of pixels in parallel to the axis. Positive values move the labels in the positive direction of the axis. ticklabelstandoff Sets the standoff distance (in px) between the axis tick labels and their default position. A positive `ticklabelstandoff` moves the labels farther away from the plot area if `ticklabelposition` is "outside", and deeper into the plot area if `ticklabelposition` is "inside". A negative `ticklabelstandoff` works in the opposite direction, moving outside ticks towards the plot area and inside ticks towards the outside. If the negative value is large enough, inside ticks can even end up outside and vice versa. ticklabelstep Sets the spacing between tick labels as compared to the spacing between ticks. A value of 1 (default) means each tick gets a label. A value of 2 means shows every 2nd label. A larger value n means only every nth tick is labeled. `tick0` determines which labels are shown. Not implemented for axes with `type` "log" or "multicategory", or when `tickmode` is "array". ticklen Sets the tick length (in px). tickmode Sets the tick mode for this axis. If "auto", the number of ticks is set via `nticks`. If "linear", the placement of the ticks is determined by a starting position `tick0` and a tick step `dtick` ("linear" is the default value if `tick0` and `dtick` are provided). If "array", the placement of the ticks is set via `tickvals` and the tick text is `ticktext`. ("array" is the default value if `tickvals` is provided). If "sync", the number of ticks will sync with the overlayed axis set by `overlaying` property. tickprefix Sets a tick label prefix. ticks Determines whether ticks are drawn or not. If "", this axis' ticks are not drawn. If "outside" ("inside"), this axis' are drawn outside (inside) the axis lines. tickson Determines where ticks and grid lines are drawn with respect to their corresponding tick labels. Only has an effect for axes of `type` "category" or "multicategory". When set to "boundaries", ticks and grid lines are drawn half a category to the left/bottom of labels. ticksuffix Sets a tick label suffix. ticktext Sets the text displayed at the ticks position via `tickvals`. Only has an effect if `tickmode` is set to "array". Used with `tickvals`. ticktextsrc Sets the source reference on Chart Studio Cloud for `ticktext`. tickvals Sets the values at which ticks on this axis appear. Only has an effect if `tickmode` is set to "array". Used with `ticktext`. tickvalssrc Sets the source reference on Chart Studio Cloud for `tickvals`. tickwidth Sets the tick width (in px). title :class:`plotly.graph_objects.layout.yaxis.Title` instance or dict with compatible properties type Sets the axis type. By default, plotly attempts to determined the axis type by looking into the data of the traces that referenced the axis in question. uirevision Controls persistence of user-driven changes in axis `range`, `autorange`, and `title` if in `editable: true` configuration. Defaults to `layout.uirevision`. visible A single toggle to hide the axis while preserving interaction like dragging. Default is true when a cheater plot is present on the axis, otherwise false zeroline Determines whether or not a line is drawn at along the 0 value of this axis. If True, the zero line is drawn on top of the grid lines. zerolinecolor Sets the line color of the zero line. zerolinewidth Sets the width (in px) of the zero line. Did you mean "tickfont"? Bad property path: titlefont ^^^^^^^^^ 进程已结束,退出代码为 1
07-31
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值