Apache ECharts 5.2.0 新特性

萬能過渡動畫

自然流暢的過渡動畫一直是 Apache ECharts 的重要特色。透過避免資料更新帶來的突變,不僅能提升視覺效果,也為表達資料的關聯、演變提供了可能。因此,在 5.2.0 中,我們進一步增強了這項動畫的能力。接下來,我們將看到這項「**萬能過渡動畫**」(Universal Transition)是如何為圖表增添表現力和敘事能力的。

在以前的版本中,過渡動畫有一定的侷限性:只能用於同一個系列(series)內相同型別的圖形的位置、尺寸等屬性的過渡,不能跨系列,也不能跨圖表型別。比如下面這個例子,透過餅圖中扇形的變化,反映了資料百分比的變化。

function makeRandomData() {
  return [
    {
      value: Math.random(),
      name: 'A'
    },
    {
      value: Math.random(),
      name: 'B'
    },
    {
      value: Math.random(),
      name: 'C'
    }
  ];
}
option = {
  series: [
    {
      type: 'pie',
      radius: [0, '50%'],
      data: makeRandomData()
    }
  ]
};

setInterval(() => {
  myChart.setOption({
    series: {
      data: makeRandomData()
    }
  });
}, 2000);
線上示例

而從 5.2.0 開始,我們引入了萬能過渡動畫這一更強大的動畫功能。有了它,過渡不再侷限於同一個系列內部。現在,我們可以利用這種跨系列的形變(morphing)動畫,在任何型別的系列、任何型別的圖形之間進行變換。

這能有多酷呢?讓我們一起來看一看!

跨系列圖表的形變(morphing)過渡

透過配置項 universalTransition: true 開啟萬能過渡動畫特性後,從餅圖切換到柱狀圖,或者從柱狀圖切換到散點圖,甚至在更復雜的旭日圖和矩形樹圖之間切換,都可以實現自然的形變效果。

如下所示,在餅圖和柱狀圖之間切換。

const dataset = {
  dimensions: ['name', 'score'],
  source: [
    ['Hannah Krause', 314],
    ['Zhao Qian', 351],
    ['Jasmin Krause ', 287],
    ['Li Lei', 219],
    ['Karle Neumann', 253],
    ['Mia Neumann', 165],
    ['Böhm Fuchs', 318],
    ['Han Meimei', 366]
  ]
};
const pieOption = {
  dataset: [dataset],
  series: [
    {
      type: 'pie',
      // associate the series to be animated by id
      id: 'Score',
      radius: [0, '50%'],
      universalTransition: true,
      animationDurationUpdate: 1000
    }
  ]
};
const barOption = {
  dataset: [dataset],
  xAxis: {
    type: 'category'
  },
  yAxis: {},
  series: [
    {
      type: 'bar',
      // associate the series to be animated by id
      id: 'Score',
      // Each data will have a different color
      colorBy: 'data',
      encode: { x: 'name', y: 'score' },
      universalTransition: true,
      animationDurationUpdate: 1000
    }
  ]
};

option = barOption;

setInterval(() => {
  option = option === pieOption ? barOption : pieOption;
  // Use the notMerge form to remove the axes
  myChart.setOption(option, true);
}, 2000);
線上示例

更多常見圖表間的過渡效果。

這種過渡不僅限於基本的折線圖、柱狀圖、餅圖,還可以在柱狀圖和地圖之間切換

或者在旭日圖和矩形樹圖之間切換,甚至在非常靈活的自定義系列之間也可以進行過渡。

注意,你需要配置系列的 id,以確保需要進行過渡動畫的系列之間有一一對應的關係。

最小化包需要手動引入該特性。

import { UniversalTransition } from 'echarts/features';
echarts.use([UniversalTransition]);

資料的分裂、合併動畫

除了常見的資料值的更新,有時我們也會遇到資料聚合、下鑽等互動後的更新,這時我們無法直接應用一對一的過渡,而是需要用分裂、合併等更多的動畫效果來表達資料的變換。

為了能夠表達資料之間可能的多對多聯絡,在 5.2.0 中我們引入了一個新的概念 groupId。我們可以透過 series.dataGroupId 給整個系列設定分組,或者更細粒度地透過 series.data.groupId 給每個資料設定所屬的分組。如果你使用 dataset 管理資料,那就更方便了,可以使用 encode.itemGroupId 指定一個維度編碼為 groupId

例如,我們要實現一個柱狀圖的下鑽動畫,我們可以將下鑽後的整個系列的資料都設定為相同的 groupId,這個 groupId 對應於下鑽前的資料

option = {
  xAxis: {
    data: ['Animals', 'Fruits', 'Cars']
  },
  yAxis: {},
  dataGroupId: '',
  animationDurationUpdate: 500,
  series: {
    type: 'bar',
    id: 'sales',
    data: [
      {
        value: 5,
        groupId: 'animals'
      },
      {
        value: 2,
        groupId: 'fruits'
      },
      {
        value: 4,
        groupId: 'cars'
      }
    ],
    universalTransition: {
      enabled: true,
      divideShape: 'clone'
    }
  }
};

const drilldownData = [
  {
    dataGroupId: 'animals',
    data: [
      ['Cats', 4],
      ['Dogs', 2],
      ['Cows', 1],
      ['Sheep', 2],
      ['Pigs', 1],
      ['Cows', 1],
      ['Sheep', 2],
      ['Pigs', 1]
    ]
  },
  {
    dataGroupId: 'fruits',
    data: [
      ['Apples', 4],
      ['Oranges', 2],
      ['Oranges', 2]
    ]
  },
  {
    dataGroupId: 'cars',
    data: [
      ['Toyota', 4],
      ['Opel', 2],
      ['Volkswagen', 2],
      ['Volkswagen', 2]
    ]
  }
];

myChart.on('click', event => {
  if (event.data) {
    const subData = drilldownData.find(data => {
      return data.dataGroupId === event.data.groupId;
    });
    if (!subData) {
      return;
    }
    myChart.setOption({
      xAxis: {
        data: subData.data.map(item => {
          return item[0];
        })
      },
      series: {
        type: 'bar',
        id: 'sales',
        dataGroupId: subData.dataGroupId,
        data: subData.data.map(item => {
          return item[1];
        }),
        universalTransition: {
          enabled: true,
          divideShape: 'clone'
        }
      },
      graphic: [
        {
          type: 'text',
          left: 50,
          top: 20,
          style: {
            text: 'Back',
            fontSize: 18
          },
          onclick: function() {
            myChart.setOption(option, true);
          }
        }
      ]
    });
  }
});
線上示例

藉助 groupId,我們還可以實現更豐富的聚合、下鑽動畫。

資料的聚合。

單個系列下鑽成兩個系列

萬能過渡動畫將表達資料關聯和演變的能力提升到了一個新的高度,為你的視覺化創作靈感插上翅膀。

看到這裡,我們知道你已經迫不及待想嘗試了。不過別急,5.2.0 還有更多值得一看的新特性。

調色盤顏色拾取策略

在上面的萬能過渡動畫例子中,你可能已經注意到了,我們用了一個之前版本沒有的配置項 colorBy。這個配置項也是我們在這個版本新增的特性,用於為系列配置不同粒度的調色盤顏色拾取。該配置專案前支援兩種策略。

  • 'series' 會按系列分配調色盤中的顏色,同一系列中的所有資料都是一個顏色。
  • 'data' 會按資料項分配調色盤中的顏色,每個資料項都會使用不同的顏色。

以前,我們為每種型別的系列固定了這種策略,例如,柱狀圖固定為 'series',餅圖固定為 'data'

而現在有了這個新功能,我們可以給柱狀圖的每個資料項分配不同的顏色。

option = {
  xAxis: {
    type: 'category',
    data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
  },
  yAxis: {
    type: 'value'
  },
  series: [
    {
      data: [120, 200, 150, 80, 70, 110, 130],
      type: 'bar',
      colorBy: 'data'
    }
  ]
};
線上示例

或者在餅圖中統一使用一種顏色。

option = {
  series: {
    type: 'pie',
    colorBy: 'series',
    radius: [0, '50%'],
    itemStyle: {
      borderColor: '#fff',
      borderWidth: 1
    },
    data: [
      {
        value: 335,
        name: 'Direct Visit'
      },
      {
        value: 234,
        name: 'Union Ad'
      },
      {
        value: 1548,
        name: 'Search Engine'
      }
    ]
  }
};
線上示例

這個配置項可以讓我們避免去尋找調色盤顏色並逐個設定的麻煩,在特定場景下或許能提供便利。我們後續會進一步增強這個配置項,提供更多的策略。

極座標系柱狀圖的標籤

在這個版本中,我們實現了極座標系上柱狀圖的標籤,並支援豐富的標籤定位配置。下面是一個在起始點顯示標籤的進度圖。

option = {
  angleAxis: {
    show: false,
    max: 10
  },
  radiusAxis: {
    show: false,
    type: 'category',
    data: ['AAA', 'BBB', 'CCC', 'DDD']
  },
  polar: {},
  series: [
    {
      type: 'bar',
      data: [3, 4, 5, 6],
      colorBy: 'data',
      roundCap: true,
      label: {
        show: true,
        // Try changing it to 'insideStart'
        position: 'start',
        formatter: '{b}'
      },
      coordinateSystem: 'polar'
    }
  ]
};
線上示例

更多標籤位置的配置。

這個靈活的標籤位置配置項極大地豐富了極座標系柱狀圖的表現力,讓文字能夠清晰地與對應的資料匹配。

餅圖無資料時的樣式

在以前的版本中,如果餅圖中沒有資料,螢幕可能完全是空白的。因為沒有任何視覺元素,使用者可能會懷疑是不是出現了 bug。

為了解決這個問題,在這個版本中,我們在沒有資料時會預設顯示一個灰色的佔位圓,以防止螢幕完全空白。我們可以透過 emptyCircleStyle 配置這個佔位圓的樣式。

option = {
  series: [
    {
      type: 'pie',
      data: [],
      // showEmptyCircle: false,
      emptyCircleStyle: {
        // change the style to empty circle
        color: 'transparent',
        borderColor: '#ddd',
        borderWidth: 1
      }
    }
  ]
};
線上示例

如果你不希望顯示這個灰色的圓,也可以設定 showEmptyCircle: false 將其關閉。

高維資料的效能增強

我們從 4.0 版本開始引入了 dataset 來管理圖表資料。然而,在一些特別高維(>100)資料的極端場景下,我們可能會遇到一些急劇的效能下降,比如下面這個透過一千個系列來視覺化一千維資料的場景(#11907),甚至可能導致卡死。

const indices = Array.from(Array(1000), (_, i) => {
  return `index${i}`;
});
const option = {
  xAxis: { type: 'category' },
  yAxis: {},
  dataset: {
    // dimension: ['date', . . indices],
    source: Array.from(Array(10), (_, i) => {
      return {
        date: i,
        ... .indices.reduce((item, next) => {
          item[next] = Math.random() * 100;
          return item;
        }, {})
      };
    })
  },
  series: indices.map(index => {
    return { type: 'line', name: index };
  })
};

這個效能問題的原因是,我們會在每個系列的底層根據需要處理高維資料集,並儲存一份處理後的資料和關於資料維度的元資訊。這意味著,在上面的例子中,1000 x 1000 個維度的資料需要被處理和儲存,這給記憶體和垃圾回收帶來了巨大的壓力,導致高維情況下效能急劇下降。

在新版本中,我們優化了這個問題,讓所有系列儘可能地共享資料集的儲存(是否共享取決於系列如何使用資料)。這個最佳化確保了記憶體不會隨著資料集維度和系列數量的增長而爆炸,顯著提升了在這種極端場景下的初始化效能。剛才描述的例子的渲染時間也減少到了可接受的 300 毫秒以內。

從這個最佳化中受益的不僅僅是這種高維場景。當使用大資料量的 dataset 時,多個系列因為資料共享而只需處理一次資料,因此也能帶來顯著的效能提升。

自定義系列的型別最佳化

自定義系列提供了一種非常靈活的方式來建立系列圖表。相比其他系列,自定義系列的學習曲線可能有點陡峭。因此,在這個版本中,我們進一步優化了自定義系列中核心方法 renderItem 的型別,透過對 renderItem 的引數和返回值的型別進行更精確的推斷,從而可以根據返回的型別推斷出可以設定哪些元素的屬性

series = {
  type: 'custom',
  renderItem(params) {
    return {
      type: 'group',
      // The group type uses children to store children of other types
      children: [
        {
          type: 'circle',
          // circle has the following configurable shape attributes
          shape: { r: 10, cx: 0, cy: 0 },
          // Configurable styles
          style: { fill: 'red' }
        },
        {
          type: 'rect',
          // rect has the following configurable shape properties
          shape: { x: 0, y: 0, width: 100, height: 100 }
        },
        {
          type: 'path',
          // Custom path shapes
          shape: { d: '...' }
        }
      ]
    };
  }
};

總結

如果你對 5.2.0 中的一些功能和最佳化感興趣,不妨更新到最新版本的 Apache ECharts 並親自嘗試一下。

如果你對 Apache ECharts 的未來發展感興趣,也可以在 GitHub Milestone 關注我們的開發計劃。歡迎加入我們成為貢獻者(在 Wiki 瞭解更多)。

完整更新日誌

檢視更新日誌

貢獻者 在 GitHub 上編輯此頁

pissangOvilia