Loading...
G2 introduced the spec configuration method in version 5.0, which will become the mainstream usage method in the future. However, there is a key issue with the current spec:
To provide more flexible graphic style configuration capabilities, G2 supports numerous function callback methods that allow users to customize styles. However, this brings a hidden danger — function configurations in spec cannot be serialized. In SSR scenarios, users expect to be able to persistently store spec configurations, which requires a string expression to describe function behavior.
const spec = {style: {// Using callback functions for flexible customization, but cannot be persistently storedfill: (d) => (d.value > 100 ? 'red' : 'green'),},};
To solve this problem, we designed and open-sourced expr. The function above can be equivalently converted to a string expression: {d.value > 100 ? 'red' : 'green'}
.
To use expressions in G2, you need to wrap the expression content with {
and }
so that G2 can recognize it as an expression to be parsed rather than an ordinary string. For example: {d.value > 100 ? "red" : "green"}
.
As developers, we are most concerned with expression syntax. We have designed a concise and intuitive template syntax. For complete details, please refer to the expr documentation, which will not be repeated here.
Currently, G2 supports expr expressions wherever function callbacks are supported. The system will parse the expressions into functions that the renderer can understand before rendering.
This presents a challenge: different functions have different parameters with varying semantics. How can we support expressions uniformly?
The expr design requires that parameters in the template syntax strictly correspond to keys in the context. However, in G2, callback function parameters are diverse. If they were simply set as datum, i, data, options, etc., the semantics would not be clear enough and would be difficult to adapt to various scenarios.
After comprehensive consideration, we adopted a non-semantic variable naming scheme — using letter variable names like a, b, c, d to represent the position order of function parameters.
In different callback functions, the actual meanings of parameters a
, b
, c
, d
will vary:
In most callbacks, such as fill
in style
and text
in labels
, a
represents the data item, b
represents the index, c
represents the entire dataset, and d
represents options:
labels: [{// Function methodtext: (datum, index, data, options) => `${datum.name}: ${datum.value}`,// Expression method - a corresponds to datum, b corresponds to index, c corresponds to datatext: "{ a.name + ': ' + a.value }",},];
In selector
of labels
, a
represents the entire dataset:
labels: [{// Function methodselector: (data) => data,// Expression methodselector: '{a}',},];
Through this unified parameter naming convention, we can consistently use expressions in different scenarios without worrying about semantic differences in parameter names.
To help developers better understand how to use expressions, here are some comparison examples of function and expression approaches in common scenarios:
// Function approachstyle: {fill: (datum) => (datum.value > 1000 ? 'red' : 'blue'),opacity: (datum) => datum.value / 2000,stroke: (datum) => (datum.category === 'A' ? 'black' : 'gray'),lineWidth: (datum) => (datum.important ? 2 : 1),}// Expression approachstyle: {fill: '{a.value > 1000 ? "red" : "blue"}',opacity: '{a.value / 2000}',stroke: '{a.category === "A" ? "black" : "gray"}',lineWidth: '{a.important ? 2 : 1}',}
// Function approachencode: {x: 'category',y: 'value',color: (datum) => (datum.value > 500 ? 'category1' : 'category2'),opacity: (datum, index) => 1 - index * 0.1,}// Expression approachencode: {x: 'category',y: 'value',color: '{a.value > 500 ? "category1" : "category2"}',opacity: '{1 - b * 0.1}',}
// Function approachlabels: [{text: (datum) => `${datum.name}: ${datum.value}`,position: (datum) => (datum.value > 1000 ? 'top' : 'bottom'),style: {fontSize: (datum) => 10 + datum.value / 200,},transform: [{ type: 'contrastReverse' }],},];// Expression approachlabels: [{text: '{a.name + ": " + a.value}',position: '{a.value > 1000 ? "top" : "bottom"}',style: {fontSize: '{10 + a.value / 200}',},transform: [{ type: 'contrastReverse' }],},];
Currently, G2 only supports expr expression writing for callback functions in the following APIs:
style
encode
labels
children
If you need to use expressions in other APIs, please submit an issue for feedback.
Here is a complete example demonstrating the powerful capabilities of expressions in practical applications:
(() => {const chart = new G2.Chart();const spec = {type: 'spaceLayer',height: 840,width: 640,data: {type: 'fetch',value:'https://gw.alipayobjects.com/os/bmw-prod/79fd9317-d2af-4bc4-90fa-9d07357398fd.csv',format: 'csv',},children: [{type: 'interval',height: 360,width: 360,legend: false,x: 280,transform: [{ type: 'stackY' }],coordinate: { type: 'theta' },scale: {color: { palette: 'spectral' },},encode: {y: 'value',color: 'name',enterDelay: '{a.value>10000000 ? a.value>20000000 ? 2000 : 1000 : 0}',},style: {stroke: '{ a.value>20000000 ? "purple" : null}',},labels: [{text: '{"*" + a.name}',radius: '{a.value>15000000 ? a.value>20000000 ? 0.6 : 0.75 : 0.9}',style: {fontSize: '{a.value>15000000 ? a.value>20000000 ? 12 : 10 : 6}',fontWeight: 'bold',},transform: [{ type: 'contrastReverse' }],},{text: '{b < c.length - 3 ? a.value : ""}',radius: '{a.value>15000000 ? a.value>20000000 ? 0.6 : 0.75 : 0.9}',style: { fontSize: 9, dy: 12 },transform: [{ type: 'contrastReverse' }],},],animate: { enter: { type: 'waveIn', duration: 600 } },},{type: 'view',height: 400,width: 540,y: 300,children: [{type: 'interval',height: 400,width: 540,legend: false,y: 300,scale: {color: { palette: 'spectral' },},encode: {y: 'value',x: 'name',color: 'name',enterDelay:'{a.value>10000000 ? a.value>20000000 ? 2000 : 1000 : 0}',},},{type: 'line',height: 400,width: 540,legend: false,y: 300,encode: { x: 'name', y: 'value' },scale: { y: { independent: true } },labels: [{text: '{a.value}',selector: '{a}',},],axis: {y: {position: 'right',grid: null,},},},],},],};chart.options(spec);chart.render();return chart.getContainer();})();