ECharts绘图解决方案——四象限散点图

需求场景

用品牌持机量和品牌净流入量两个维度来帮助运营分析所选品牌的竞争能力健康度。

  • 竞争能力健康度:判断品牌具备正向竞争能力,还是处于危险状态。
  • 维度呈现
    横坐标:品牌持机量,中心点为取所有11个品牌持机量的中位数(注:一期为取“平均数”);
    纵坐标:品牌净流入量=本月流入量-本月流出量,中心点为0,即向上为净流入量增加,向下为净流入量减少;
    四个象限:分别表示“上升品牌”、“潜力品牌”、“高危品牌”、“下滑品牌”。

最终实现效果如下图所示:
品牌健康度分析最终效果图

需求拆解(从实现的角度)

  • 确定使用的基本图表类型及数据格式:以散点图为基础;每个散点的数据对应一个(品牌)名称和(坐标)值。
  • 四象限划分:确定横、纵轴的分割临界点。
  • 确定散点的样式、交互效果:每个散点大小一致;处于不同象限的散点使用指定不同颜色区分(此时注意考虑散点处于临界点的情况);散点上显示名称,hover时显示对应维度值信息。
  • 使用图例展示对应象限表示的含义。

问题及解决方案

  • 后续示例基于以下模拟数据做调整:
    let data = [
      {
        name: '三星',
        value: [10.0, -6.04],
      },
      {
        name: '一加',
        value: [8.0, 1.95],
      },
      {
        name: '华为',
        value: [12.0, 7.58],
      },
      {
        name: '苹果',
        value: [15.0, -7.81],
      }
    ]
    

问题一:四象限的划分,用ECharts原生提供的xAxis、yAxis属性无法做到。

因为x轴、y轴的位置是不可按具体值灵活控制的。
例如y轴在左侧时,如果x轴上的最小值<=0,则y轴位于x=0上;否则按xAxis.min的设定规则(即坐标轴刻度最小值,不设置时会自动计算最小值保证坐标轴刻度的均匀分布)计算。
几种情况的效果示例如下图所示:

x轴上的最小值<=0时

x轴上的值均大于0,且无设置xAxis.min时
x轴上的值均大于0,且设置了xAxis.min时

  • 思路:利用其它属性来模拟轴?
  • 方案:由于markLine属性的位置完全可控,可使用markLine模拟划分象限的轴。参考文档series-scatter.markLine.data中笛卡尔坐标系的相关配置说明。
  • 相关代码片段:
    const median = 11.0 // 实际值由后台计算给出,为固定值
    /* 隐藏默认的x轴和y轴 */
    xAxis: {
      show: false
    },
    yAxis: {
      show: false
    },
    /* 注:以下配置项位于series内 */
    label: {
      show: true,
      formatter: (param) => param.name || '未命名'
    }, // 显示名称
    symbolSize: 50, // 散点大小
    type: 'scatter',
    data,
    markLine: {
      lineStyle: {
        normal: {
          color: '#555555',
          type: 'solid'
        }
      },
      data: [
        {
          xAxis: median,
          // 注释为一期实现
          // type: 'average',
          // valueDim: 'x',
          label: {
            normal: {
              show: true,
              formatter: '品牌净流入量',
            },
          }
        },
        {
          yAxis: 0,
          label: {
            normal: {
              show: true,
              formatter: '品牌持机量',
            }
          }
        },
      ]
    },
    
  • 脑洞成果:

问题二:左侧视野出现的大片空白,在本图中无意义。

  • 思路:将y轴偏移到所有数据的横轴最小值。
  • 方案:利用问题一总结出来的规则即可解决。
  • 相关代码片段:
    xAxis: {
      min: 'dataMin' // 取数据在该轴上的最小值作为最小刻度
    },
    
  • 脑洞成果:

问题三:处于不同象限的散点使用指定不同颜色区分

  • 思路:通过对每个点表示的坐标值分别与临界值比较,从而确定所在区间,设置相应的颜色值。
  • 方案:与产品经理确认,处于临界值的点按偏“右、上”规则处理。
  • 相关代码片段:
    const xSeparate = median
    const ySeparate = 0
    const getAreaPointColor = (value = [0, 0]) => {
      const [x, y] = value
      switch ([x, y]) {
        case (x >= xSeparate && y >= ySeparate):
          return '#3583FF'
        case (x < xSeparate && y >= ySeparate):
          return '#33BB7B'
        case (x < xSeparate && y < ySeparate):
          return '#FB7962'
        case (x >= xSeparate && y < ySeparate):
          return '#8560C5'
      }
    }
    data = data.map(d => ({
      ...d,
      itemStyle: {
        color: getAreaPointColor(d.value)
      }
    }))
    
  • 脑洞成果:

问题四:使用不同颜色图例展示对应不同象限的含义的实现方式及其限制。

首先,查看图例legend的配置规则:“图例组件展现了不同系列的标记(symbol),颜色和名字”,这说明了展示多个图例需要依赖于有多组series。

  1. 基于一期需求(横坐标以所选品牌持机量的平均数来分割),参考series-scatter.markLine.data.0.type可知,取平均数是基于设置了markLine的该组数据计算的,此时数据只能放在一组series中才有意义,否则横坐标的分割线会永远处于加了平均数配置的那组数据的平均值上(可看以下反例帮助理解),因此图例的展示就不能用ECharts提供的legend属性实现了。
  • 反例1:series的每项都加上平均数配置时


    出现多条“纵轴”
  • 反例2:只给其中一个子项加上平均数配置时
    只有一条轴,但效果显然不是我们想要的
  • 思路:在不需要图例的任何交互的情况下,可简单地通过将图例作为渲染的Canvas以外的dom元素来实现;否则需要考虑更复杂的实现(衡量实现成本)。
  • 方案:直接用html+css实现。
  1. 基于第二版需求(横坐标以所选固定的11个品牌持机量的中位数来分割)
    • 思路:由于markLine.xAxis取固定值,因此根据象限拆分多组series变得可行了。
    • 方案:实现一个根据象限拆分多组series的函数,图例信息从series获取即可。

问题五:将散点整体缩小后,由于markLine层级过高,导致散点文字被遮挡。

  • 思路:将散点元素的层级调高或调低markLine的层级。
  • 方案:markLine层级暂无法调整,使用series-scatter.z将散点层级适当调高即可。
  • 相关代码片段:
    label: {
      show: true,
      fontSize: 8,
      formatter: (params) => params.name
    },
    symbolSize: 28,
    z: 9, // 将散点层级调高至不被markLine遮挡
    
  • 脑洞成果:

效果样例

可直接粘贴在https://echarts.apache.org/examples/zh/editor.html?c=line-simple
查看效果。

  • 样例1:简版配置(省略legend、tooltip),可作为研究基础
const median = 11.0
option = {
  title: {
    text: '品牌健康度分析',
  },
  xAxis: {
    min: 'dataMin',
    show: false
  },
  yAxis: {
    show: false
  },
  series: [{
    label: {
      show: true,
      formatter: (param) => param.name || '未命名'
    },
    symbolSize: 50,
    z: 9,
    data: [
      {
        name: '三星',
        value: [10.0, -6.04],
        itemStyle: {
          color: '#FB7962'
        }
      },
      {
        name: '一加',
        value: [8.0, 1.95],
        itemStyle: {
          color: '#33BB7B'
        }
      },
      {
        name: '华为',
        value: [12.0, 7.58],
        itemStyle: {
          color: '#3583FF'
        }
      },
      {
        name: '苹果',
        value: [15.0, -7.81],
        itemStyle: {
          color: '#8560C5'
        }
      },
      // [9.0, 8.81],
      // [11.0, 8.33],
      // [14.0, 9.96],
      // [6.0, 7.24],
      // [4.0, -4.26],
      // [12.0, 10.84],
      // [7.0, 4.82],
      // [5.0, 5.68]
    ],
    type: 'scatter',
    markLine: {
      lineStyle: {
        normal: {
          color: '#555555',
          type: 'solid'
        }
      },
      data: [
        {
          xAxis: median,
          label: {
            normal: {
              show: true,
              formatter: '品牌净流入量',
            },

          }
        },
        {
          yAxis: 0,
          label: {
            normal: {
              show: true,
              formatter: '品牌持机量',
            }
          }
        },
      ]
    },
  }]
}

  • 样例2:最终版
// 思路已在本文提供,具体业务代码不公开
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容