0
点赞
收藏
分享

微信扫一扫

Pyecharts使用JScode回调函数时遇到的函数内换行符失效问题

律楷粑粑 2022-04-18 阅读 56

前记:这个准确来说不是问题,是bug……我愿意把它评为我今年遇到的最折磨人的bug;这个bug很少有人能踩坑,原因主要是bug触发的条件比较苛刻(看题目一长串就知道了):

  1. 需要在pyecharts中使用JScode(一般来说,当label、tooltips里面想要显示一些特殊的内容时,而formatter的字符串模板形式并不支持时,只能用jscode来实现)
  2. JScode中返回的字符串有换行需求,即插入换行符“\n”
  3. 使用pyecharts时习惯先创建一个pyecharts对象,然后再通过对象调用渲染系列方法(render(),render_notebook(),render_embed())得到图像,如下风格:
    from pyecharts.charts import Bar
    
    bar = (
        Bar()
        .add_xaxis(["衬衫", "羊毛衫", "雪纺衫", "裤子", "高跟鞋", "袜子"])
        .add_yaxis("商家A", [5, 20, 36, 10, 75, 90])
    )
    bar.render()

    而不是用这种直接渲染,不使用变量指向该对象的方法:
     Bar() .add_xaxis(["衬衫", "羊毛衫", "雪纺衫", "裤子", "高跟鞋", "袜子"])
        .add_yaxis("商家A", [5, 20, 36, 10, 75, 90]) 
        .render()
  4. 对创建的pyecharts对象使用了任意两次渲染方法,即:
    from pyecharts.charts import Bar
    
    bar = (
        Bar()
        ……
    )
    
    bar.render()
    bar.render_notebook()

最后来把这四点写在一起来个bug暴雷示例,就是:

from pyecharts.charts import Bar
from pyecharts.commons.utils import JsCode

bar = (
    Bar()
    .add_xaxis(
    ……
    label_opts=opts.LabelOpts(
    ……
        formatter=JsCode("function(x){……return '\\n'……}"),
    ……
)
bar.render()
bar.render_notebook()

真实情况远比这个复杂得多,因为一个pyecharts对象里面还要配置其它项目,当bug第一次发生的时候,很难第一时间就排除代码的其它部分,直接想到bug的源头是在这里

 1 实际出bug的代码介绍

from pyecharts import options as opts
from pyecharts.charts import TreeMap
from pyecharts.commons.utils import JsCode

c = (TreeMap()
    .add("RFM各客户价值类型人数", data,
         tooltip_opts=opts.TooltipOpts(
             formatter=
                 JsCode(
                 """
                 function(x){
                 return
                 x.data.name +':'+x.data.value+'人<br> ('
                 +(Number(x.data.value)/Number(x.treePathInfo[0].value)*100).toFixed(2)+ '%)';}
                 """
             ),
             textstyle_opts=opts.TextStyleOpts(
                 align="center"
             )
         ),
         label_opts=opts.LabelOpts(
             formatter=JsCode(
                 "function(x){return x.data.name+'\n'+x.data.value+'人 ('+(Number(x.data.value)/Number(x.treePathInfo[0].value)*100).toFixed(2)+ '%)';}"
         ),
             position="inside"
        ),
        )
    .set_global_opts(
        toolbox_opts=opts.ToolboxOpts(
            feature=opts.ToolBoxFeatureOpts(
                data_zoom=None,
                brush=None,
                magic_type=None,
            )
        )
    )
)
c.render()
c.render_notebook()

 注意本行代码

formatter=JsCode
(
"function(x){return x.data.name+'\n'+x.data.value+'人 ('+Number(x.data.value)/Number(x.treePathInfo[0].value)*100).toFixed(2)+ '%)';}"
)

希望返回的数据格式应该是在中间换行

但现实猛然打脸

2 漫漫排bug之路

2.1 寻找换行符\n

看到这个现象,第一反应就是“忘了加换行符\n”,但是瞪大了我的眼睛确认了好几遍,也还是确定我在正确的位置加了换行符

 2.2 确定换行符格式和可行性

因为JScode本质上是写一串JavaScript代码,pyecharts不会使用python内核对这行代码执行,只是简单处理后交给JavaScript来执行,那么会不会是因为这个过程,导致换行符有特殊要求,我没有遵循这个要求从而失效了?

带着这个猜想查找官方文档,首先确认了,JScode是支持换行的

 

然后在官方文档继续查找,看到了这句话

 

“柳暗花明又一村”!(不得不说有点坑哈,在api那里说可以直接用\n换行,在这里才说需要用\\n换行)

满怀欣喜地把对应代码改成,然后准备迎接排除bug的喜悦

formatter=JsCode
(
"function(x){return x.data.name+'\\n'+x.data.value+'人 ('+Number(x.data.value)/Number(x.treePathInfo[0].value)*100).toFixed(2)+ '%)';}"
)

然而……

没错,问题照旧(所以这里的图都懒得重新截图,还是上面那张)

【小小剧透,其实一般人到这里应该已经完美解决了,但是某个隐藏的bug仍然阻止了换行,所以当时的我丝毫没有意识到这点;这波是bug上加bug】

2.3 试图寻找问题的根源

上面说过,pyecharts其实是基于echarts开发的,使用echarts的原理是,echarts首先用js开发了一个自己的绘图库,用户只需要按照js库的功能接口规范编写好调用代码,交给echarts.js执行即可,echarts.js会把绘图渲染的结果渲染到浏览器上。而pyecharts做的事情就是把用户写的pyecharts代码封装成对应的js代码。

那么,我在pyecharts里面写好的代码,是不是在转换到echarts的时候变了样呢?【剧透:是的】

于是,我用pycharm打开了这个代码(之前是在jupyter notebook里面写的,因为方便看到实时渲染结果),然后运行代码后查看变量——生成的pyecharts对象,这一看还真看出了端倪:

  • 首先找到变量c,即我创建的这个pyecharts对象
  • 然后查看c的属性,找到由上面的代码生成的JScode

    (有点难找,在c-options-series-0-label-opts-formatter-js_code里面)
  • 仔细一看还真看出了问题,我在pyecharts代码里面写的"\\n"在运行后,生成的对象里面变成了""空字符串?
    (严格来说,图中显示的是\"\",因为要对引号转义,这里方便叙述,就没有带上转义符号)

 2.4-2.9 ……令人抓狂的试错

虽然知道了代码运行后,生成的pyecharts对象里面会莫名把我写的换行符给“吃掉”,但如何避免这个情况,博主在这过程中一直一筹莫展,查了某度,试了各种玄学方法,例如

 都毫无作用

3.3 成也凑巧,败也凑巧

在被这个bug折磨了3小时后,博主无意中发现,虽然jupyter notebook里面实时渲染的图形一直显示有问题,但是博主还使用render()方法生成了render.html啊,那么打开这个html文件会不会有啥不同呢?

 ohhhhhhh!

bug终于消失了,但是为什么呢?难道是render_notebook()这个渲染方法单独出了bug吗?

在经历一系列走弯路后(博主又花了1.5小时,主要原因是先入为主地认为问题出在具体的方法上面,没有考虑到别的地方,然后显而易见地走错了方向,一直没有收获;但是博主一开始并没有觉得这个debug失败是思考方向有问题所致,反而觉得这个bug是偶发性bug)

后来,又是一次偶然,发现问题出在对同一个pyecharts对象渲染了两次的时候,意思是说

结论:使用JScode时,生成的Pyecharts对象被任意render类的方法渲染一次后,其内部变量JScode字符就会变化,失去换行符\n,从而当其再次被渲染时,渲染出来的图形对应文字部分不再换行

说这次debug过程是“成也凑齐,败也凑巧”,是因为这个bug出发条件实际上很严苛,触发它本身就是凑巧;而“成也凑巧”是因为发现这个bug真正的原因也是凑巧发现的——我因为找bug一无所成,一起之下砸了下键盘,不小心碰到了“D”键,删除了原来的代码,还关了网页和kernel……;然后我赶紧凭着记忆写了一份,在末尾无意中调换了c.render()和c.render_notebook()的顺序,这才观察到上面的这个现象,才敢确定真正的bug源头在于渲染的顺序和次数。

由于官方没有任何文档说到这个特性,而且从pyecharts的设计思路来看,pyecharts绘图对象应该是可以重复渲染图形而本身而不受任何影响的,所以基本认定这个现象为bug

后记:大家对如何写JScode来实现自定义功能感兴趣吗?虽然这次的bug就是JScode引出的,但是遇到pyecharts自带的设置不能满足绘图要求时,我还是觉得JScode真香!

举报

相关推荐

0 条评论