ECharts繪圖解決方案——四象限散點圖

需求場景

用品牌持機量和品牌凈流入量兩個維度來幫助運營分析所選品牌的競爭能力健康度。

  • 競爭能力健康度:判斷品牌具備正向競爭能力,還是處于危險狀態(tài)。
  • 維度呈現(xiàn)
    橫坐標:品牌持機量,中心點為取所有11個品牌持機量的中位數(shù)(注:一期為取“平均數(shù)”);
    縱坐標:品牌凈流入量=本月流入量-本月流出量,中心點為0,即向上為凈流入量增加,向下為凈流入量減少;
    四個象限:分別表示“上升品牌”、“潛力品牌”、“高危品牌”、“下滑品牌”。

最終實現(xiàn)效果如下圖所示:
品牌健康度分析最終效果圖

需求拆解(從實現(xiàn)的角度)

  • 確定使用的基本圖表類型及數(shù)據(jù)格式:以散點圖為基礎;每個散點的數(shù)據(jù)對應一個(品牌)名稱和(坐標)值。
  • 四象限劃分:確定橫、縱軸的分割臨界點。
  • 確定散點的樣式、交互效果:每個散點大小一致;處于不同象限的散點使用指定不同顏色區(qū)分(此時注意考慮散點處于臨界點的情況);散點上顯示名稱,hover時顯示對應維度值信息。
  • 使用圖例展示對應象限表示的含義。

問題及解決方案

  • 后續(xù)示例基于以下模擬數(shù)據(jù)做調(diào)整:
    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軸在左側(cè)時,如果x軸上的最小值<=0,則y軸位于x=0上;否則按xAxis.min的設定規(guī)則(即坐標軸刻度最小值,不設置時會自動計算最小值保證坐標軸刻度的均勻分布)計算。
幾種情況的效果示例如下圖所示:

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內(nèi) */
    label: {
      show: true,
      formatter: (param) => param.name || '未命名'
    }, // 顯示名稱
    symbolSize: 50, // 散點大小
    type: 'scatter',
    data,
    markLine: {
      lineStyle: {
        normal: {
          color: '#555555',
          type: 'solid'
        }
      },
      data: [
        {
          xAxis: median,
          // 注釋為一期實現(xiàn)
          // type: 'average',
          // valueDim: 'x',
          label: {
            normal: {
              show: true,
              formatter: '品牌凈流入量',
            },
          }
        },
        {
          yAxis: 0,
          label: {
            normal: {
              show: true,
              formatter: '品牌持機量',
            }
          }
        },
      ]
    },
    
  • 腦洞成果:

問題二:左側(cè)視野出現(xiàn)的大片空白,在本圖中無意義。

  • 思路:將y軸偏移到所有數(shù)據(jù)的橫軸最小值。
  • 方案:利用問題一總結(jié)出來的規(guī)則即可解決。
  • 相關代碼片段:
    xAxis: {
      min: 'dataMin' // 取數(shù)據(jù)在該軸上的最小值作為最小刻度
    },
    
  • 腦洞成果:

問題三:處于不同象限的散點使用指定不同顏色區(qū)分

  • 思路:通過對每個點表示的坐標值分別與臨界值比較,從而確定所在區(qū)間,設置相應的顏色值。
  • 方案:與產(chǎn)品經(jīng)理確認,處于臨界值的點按偏“右、上”規(guī)則處理。
  • 相關代碼片段:
    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)
      }
    }))
    
  • 腦洞成果:

問題四:使用不同顏色圖例展示對應不同象限的含義的實現(xiàn)方式及其限制。

首先,查看圖例legend的配置規(guī)則:“圖例組件展現(xiàn)了不同系列的標記(symbol),顏色和名字”,這說明了展示多個圖例需要依賴于有多組series。

  1. 基于一期需求(橫坐標以所選品牌持機量的平均數(shù)來分割),參考series-scatter.markLine.data.0.type可知,取平均數(shù)是基于設置了markLine的該組數(shù)據(jù)計算的,此時數(shù)據(jù)只能放在一組series中才有意義,否則橫坐標的分割線會永遠處于加了平均數(shù)配置的那組數(shù)據(jù)的平均值上(可看以下反例幫助理解),因此圖例的展示就不能用ECharts提供的legend屬性實現(xiàn)了。
  • 反例1:series的每項都加上平均數(shù)配置時


    出現(xiàn)多條“縱軸”
  • 反例2:只給其中一個子項加上平均數(shù)配置時
    只有一條軸,但效果顯然不是我們想要的
  • 思路:在不需要圖例的任何交互的情況下,可簡單地通過將圖例作為渲染的Canvas以外的dom元素來實現(xiàn);否則需要考慮更復雜的實現(xiàn)(衡量實現(xiàn)成本)。
  • 方案:直接用html+css實現(xiàn)。
  1. 基于第二版需求(橫坐標以所選固定的11個品牌持機量的中位數(shù)來分割)
    • 思路:由于markLine.xAxis取固定值,因此根據(jù)象限拆分多組series變得可行了。
    • 方案:實現(xiàn)一個根據(jù)象限拆分多組series的函數(shù),圖例信息從series獲取即可。

問題五:將散點整體縮小后,由于markLine層級過高,導致散點文字被遮擋。

  • 思路:將散點元素的層級調(diào)高或調(diào)低markLine的層級。
  • 方案:markLine層級暫無法調(diào)整,使用series-scatter.z將散點層級適當調(diào)高即可。
  • 相關代碼片段:
    label: {
      show: true,
      fontSize: 8,
      formatter: (params) => params.name
    },
    symbolSize: 28,
    z: 9, // 將散點層級調(diào)高至不被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:最終版
// 思路已在本文提供,具體業(yè)務代碼不公開
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容