recharts 是从Yihui Xie fork而来。它基于百度Echarts2的最后一个稳定发布版(v2.2.7)开发。本文档始终反映recharts最新的特性(Github)。基于Echarts3的recharts2包仍在开发中。

安装方法:

if (!require(devtools)) library(devtools)
install_github("madlogos/recharts")

2 Hello World

recharts是一个用于可视化的R加载包,它提供了一套面向JavaScript库ECharts2的接口。此包的目的是让R用户即便不精通HTML或JavaScript,也能用很少的代码做出Echarts交互图——当然,懂一点JavaScript的话会更如虎添翼。下面这个散点图展示了本包的基本语法:

library(recharts)
echartr(iris, Sepal.Length, Sepal.Width, series = Species)

通过browseVignettes("recharts")可以离线查看本手册。

recharts建基于htmlwidgets包之上,这样做的优点是极大地节省了开发者管理JavaScript依赖包和处理不同类型的输出文档(如R Markdown和Shiny)的时间。你只需要创建一幅图,而如何输出这幅图(无论R Markdown, Shiny, 还是R控制台/ RStudio)则交由htmlwidgets来处理。

此包的主函数是echartr()和S3通用函数echart()。在设计宗旨上,我们希望它们能自动处理不同类型的R数据。比如,当把一个数据框传入echart(),而x/y变量均为数值型,它们会自动适配散点图,并自动生成对应的坐标轴。当然,你也可以覆盖自动适配的结果。

3 快速入门Quick Start

试着按下面的步骤制作你的第一幅Echarts交互图。

3.1 数据Data

制图总是始于数据本身。我们用datasets包自带的mtcars数据集。你可以输入命令?mtcars查看数据集的结构。

head(mtcars)
##                    mpg cyl disp  hp drat    wt  qsec vs am gear carb
## Mazda RX4         21.0   6  160 110 3.90 2.620 16.46  0  1    4    4
## Mazda RX4 Wag     21.0   6  160 110 3.90 2.875 17.02  0  1    4    4
## Datsun 710        22.8   4  108  93 3.85 2.320 18.61  1  1    4    1
## Hornet 4 Drive    21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
## Hornet Sportabout 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2
## Valiant           18.1   6  225 105 2.76 3.460 20.22  1  0    3    1

我们想知道wt (weight)和mpg (miles per gallon)的关联,那么散点图是一个不错的选择。这要求x和y都是数值型。

3.2 基础图形Core Chart

echartr构建基础图形。

echartr(mtcars, wt, mpg)

echartr的语法是:

## function (data, x = NULL, y = NULL, series = NULL, weight = NULL, 
##     facet = NULL, t = NULL, lat = NULL, lng = NULL, type = "auto", 
##     subtype = NULL, elementId = NULL, ...)

3.2.1 参数Arguments

参数 解释

data

源数据,必须是数据框

x

自变量。data的一列或多列。可以是时间、数值或文本型。

  • 直角坐标系里,x与x轴关联
  • 在极坐标系,x与极坐标关联
  • 其他类型中,单坐标系可参考直角坐标系的例子,而多坐标系可参考极坐标系的例子。

y

应变量,data的一列或多列。始终为数值型。

series

分组变量,data的某一列。进行运算时被视作因子。作为数据系列,映射到图例。

weight

权重变量,在气泡图、线图、柱图中与图形大小关联。

facet

分面变量,data的某一列。进行运算时被视为因子。适用于多坐标系,facet的每个水平会生成一个独立的分面。

t

时间轴变量。一旦指定t变量,就会生成时间轴组件。

lat

纬度,用于地图/热力图

lng

经度,用于地图/热力图

type

图类型,默认为’auto’。type作为向量传入时,映射到series向量;作为列表传入时,映射到facet向量。

subtype

图亚类,默认为NULL。subtype作为向量传入时,映射到series向量;作为列表传入时,映射到facet向量。

3.2.2 支持的图类型Supported Chart Types

echartr目前支持下列图类型 (’name’列的正则样式,不分大小写)。’type’列则是目前Echarts2所支持的图类型。

knitr::kable(recharts:::validChartTypes[,c(1:3,5)])
id name type misc
1 ^(scatter|point)$ scatter
2 ^(bubble)$ scatter bubble
3 ^(bar|hbar)$ bar flip
4 ^(vbar|column)$ bar
5 ^(histogram|hist)$ hist
6 ^(line)$ line
7 ^(curve)$ line smooth
8 ^(area)$ line fill
9 ^(wave)$ line fill_smooth
10 ^(map_world|world_map)$ map world
11 ^(map_china|china_map)$ map china
12 ^(map_world_multi|world_map_multi)$ map world_multi
13 ^(map_china_multi|china_map_multi)$ map china_multi
14 ^(map)$ map geojson
15 ^(k|candlestick)$ k
16 ^(pie)$ pie
17 ^(ring)$ pie ring
18 ^(rose)$ pie rose
19 ^(chord)$ chord
20 ^(force|force_curve)$ force
21 ^(force_line)$ force line
22 ^(funnel)$ funnel
23 ^(pyramid)$ funnel ascending
24 ^(tree|vtree|tree_vertical)$ tree
25 ^(htree|tree_horizontal)$ tree horizontal
26 ^(tree_inv|vtree_inv|tree_vertical_inv)$ tree inv
27 ^(htree_inv|tree_horizontal_inv)$ tree horizontal_inv
28 ^(treemap)$ treemap
29 ^(wordcloud)$ wordCloud
30 ^(heatmap)$ heatmap
31 ^(radar|spider|star)$ radar
32 ^(gauge|dashboard)$ gauge
33 ^(eventriver)$ eventRiver
34 ^(venn)$ venn

指定data以及xyechartr就会自动调用recharts:::series_scatter函数处理数据。

如果指定series,就会产生一幅分组散点图。

echartr(mtcars, wt, mpg, factor(am, labels=c('Automatic', 'Manual')))

如果指定weight,并设typebubble,就生成一幅气泡图。

echartr(mtcars, wt, mpg, am, weight=gear, type='bubble')
## Warning in split_indices(.group, .n): '.Random.seed' is not an integer
## vector but of type 'NULL', so ignored

3.2.3 特别注意Special Notes

3.2.3.1 参数列表Param List

输入参数列表的格式包括

  • 表达式: wtmpg 甚至复杂如factor(am, labels=c('Automatic', 'Mannual'))
  • 文本: 'wt''mpg'
  • 公式: ~wt~mpg

3.2.3.2 数据要求Data Requirements

数据必须以数据框形式传入。数据系列和时间轴会以seriest变量的原始顺序排序。所以作图前,必须先对数据框进行相应的排序,以确保作出的图里,图例和时间轴以正确的顺序显示。

用到时间轴(t)时,务必格外小心。xyseriesweight变量一旦用到,就要确保每个t水平上,这些变量都完备地排列组合了。否则你会发现各组图形在时间轴上莫名其妙地重叠呈现。

3.2.3.3 图类和亚类Type/Subtype

3.2.3.3.1 类和亚类混合

recharts在某些情况下支持混合作图,比如柱图和线图。

目前recharts只支持下列混合 - 直角坐标系图类:散点/气泡、柱/条图、线/面积图 - 极坐标图类:漏斗图、饼/环图 - 其他:力导向图、和弦图

d <- data.table::dcast(mtcars, carb+gear~., mean, value.var='mpg')
names(d)[3] <- 'mean.mpg'
d$carb <- as.character(d$carb)
echartr(d, carb, "mean.mpg", gear, type=c('vbar', 'vbar', 'line')) %>% 
    setSymbols('emptycircle')

也可以混合亚类。参见recharts:::validChartTypessubtype列。你可以用’+‘、’_‘或’|’组合多个亚类,比如’stack+smooth’可以用来生成堆积平滑线图。

echartr(d, carb, mean.mpg, gear, type='line', 
        subtype=c('stack + smooth', 'stack + dotted', 'smooth + dashed')) %>%
    setSymbols('emptycircle')
3.2.3.3.2 类/亚类映射到分面(facet)

上面的例子显示了如何将有类/亚类映射到数据系列(series)。如果你想将类/亚类映射到分面,需要将它们写成列表(list),而列表中的每个向量,则依次映射到数据系列(series)。即list[[1]], list[[2]], …映射分面1、2, …,而list[[1]]的1、2、…向量则映射系列1、2、…。

如facet为2个水平,series为3个水平,意味着type和subtype应为list(c(s1,s2,s3), c(s1,s2,s3))结构。recharts会尝试将传入参数type和subtype进行补齐或截断,以达到一一映射。

系列/分面 分面1 分面2 分面i
系列_1_ list[[1]][1] list[[2]][1] list[[i]][1]
系列_2_ list[[1]][2] list[[2]][2] list[[i]][2]
系列_j_ list[[1]][j] list[[2]][j] list[[i]][j]
d1 <- data.frame(x=rep(LETTERS[1:6], 4), y=abs(rnorm(24)), 
                 f=c(rep('i', 12), rep('ii', 12)), 
                 s=rep(c(rep('I', 6), rep('II', 6)), 2))
echartr(d1, x, y, s, facet=f, type='radar', 
        subtype=list(c('fill', ''), c('', 'fill')))

3.3 修改作图Modify the Chart

你可以将基本图形存为一个对象,然后不断修改它,并把这些修改用%>%串联起来。

g = echartr(mtcars, wt, mpg, factor(am, labels=c('Automatic', 'Manual')))

3.3.1 调整Tune Series

上述命令创建了一个Echarts对象g,包含两个数据系列:‘Automatic’和’Manual’。g作为一个列表,其数据结构为:

- x
  |-- series
      |--- list 1
           |---- name: 'Automatic'
           |---- data: ...
           |---- type: 'scatter'
      |--- list 2
           |---- name: 'Manual'
           |---- data: ...
           |---- type: 'scatter'
      |--- ...
  |-- legend
  |-- xAxis
  |-- yAxis
  |-- grid  
  |-- tooltip
  |-- ...

如果你想直接修改数据系列的定义,可以调用低级函数setSeries

我们把’Manual’系列(索引号为2)的symbolSize改为8,symbolRotate改为30。

g %>% setSeries(series=2, symbolSize=8, symbolRotate=30)

3.3.2 标注线markLine

给两个数据系列分别添加各自的均数标注线。

g %>% addMarkLine(data=data.frame(type='average', name1='Avg'))

3.3.3 标注点markPoint

给第一个数据系列(‘Automatic’)标出最大值的点。

g %>% addMarkPoint(series=1, data=data.frame(type='max', name='Max'))

该命令也可以写作

g %>% addMarkPoint(series='Automatic', data=data.frame(type='max', name='Max'))

3.3.4 标题Title

添加标题(红色)和副标题(超级链接到 https://stat.ethz.ch/R-manual/R-devel/library/datasets/html/mtcars.html)。

link <- 'https://stat.ethz.ch/R-manual/R-devel/library/datasets/html/mtcars.html'
g %>% setTitle('wt vs mpg', paste0('[Motor Trend](', link, ')'), 
               textStyle=list(color='red'))

3.3.5 图例Legend

修改图例(青柠色),初始化时只选中第一系列(‘Automatic’)。

g %>% setLegend(selected='Automatic', textStyle=list(color='lime'))

3.3.6 工具箱Toolbox

修改工具箱显示语言为英文,并置于交互图右上角,垂直显示。

关于pos变量如何定义,请参考vecPos函数。

g %>% setToolbox(lang='en', pos=2)

3.3.7 数据缩放漫游DataZoom

添加缩放漫游控件(初始时不显示).

g %>% setDataZoom()

3.3.8 坐标轴Axis

调整坐标轴,使x-和y-坐标交叉于零点。

g %>% setXAxis(min=0) %>% setYAxis(min=0)

3.4 美化Fortify the Chart

3.4.1 主题Theme

使用’dark’主题。可以选择的自带主题包括“macarons”, “infographic”, “blue”, “dark”, “gray”, “green”, “helianthus”, “macarons2”, “mint”, “red”, “roma”, “sakura”, “shine”, 和 “vintage”。

拖曳重算(Calculable)是Echarts特有的交互方式。在某些图(如饼图)中,效果比较好。

g %>% setTheme('dark', calculable=TRUE)

3.4.2 图标Symbols

把第1系列(‘Automatic’)的图标改为’heart’,第2系列(‘Manual’)的图标改为’star6’。

g %>% setSymbols(c('heart', 'star6'))

3.5 合起来Altogether

你可以把上述步骤用%>%合起来。如果你对JavaScript很熟悉,你可以把JavaScript片段包在JS()函数中,以获得更好的效果。

g %>% setSeries(series=2, symbolSize=8, symbolRotate=30) %>% 
    addMarkLine(data=data.frame(type='average', name1='Avg')) %>%
    addMarkPoint(series=1, data=data.frame(type='max', name='Max')) %>%
    setTitle('wt vs mpg', paste0('[Motor Trend](', link, ')'), 
               textStyle=list(color='red')) %>%
    setLegend(selected='Automatic', textStyle=list(color='lime')) %>%
    setToolbox(lang='en', pos=2) %>% setDataZoom() %>% 
    setTheme('dark', calculable=TRUE) %>% setSymbols(c('heart', 'star6'))

4 Low-level API

4.1 从头创建De Novo

虽然ECharts支持很多种图,但要娴熟地用echartr()创建它们仍可能花不少时间。 所以recharts也为list提供了一个低级的S3方法实现。由于ECharts的主要用法是传入一个JavaScript对象给.setOption()方法,而在R中,这样一个对象是用list构建的。所以echart.list()这个低级方法能让用户任意创建自定义图形。下面是一个简单的和弦图的例子,取材于http://echarts.baidu.com/echarts2/doc/example/chord1.html:

chordEx1 = list(
  title = list(
    text = '测试数据',
    subtext = 'From d3.js',
    x = 'right',
    y = 'bottom'
  ),
  tooltip = list(
    trigger = 'item',
    formatter = JS('function(params) {
      if (params.indicator2) { // is edge
        return params.value.weight;
      } else {// is node
        return params.name
      }
    }')
  ),
  toolbox = list(
    show = TRUE,
    feature = list(
      restore = list(show = TRUE),
      magicType = list(show = TRUE, type = c('force', 'chord')),
      saveAsImage = list(show = TRUE)
    )
  ),
  legend = list(
    x = 'left',
    data = c('group1', 'group2', 'group3', 'group4')
  ),
  series = list(
    list(
      type = 'chord',
      sort = 'ascending',
      sortSub = 'descending',
      showScale = TRUE,
      showScaleText = TRUE,
      data = list(
        list(name = 'group1'),
        list(name = 'group2'),
        list(name = 'group3'),
        list(name = 'group4')
      ),
      itemStyle = list(
        normal = list(
          label = list(show = FALSE)
        )
      ),
      matrix = rbind(
        c(11975,  5871, 8916, 2868),
        c( 1951, 10048, 2060, 6171),
        c( 8010, 16145, 8090, 8045),
        c( 1013,   990,  940, 6907)
      )
    )
  )
)

echart(chordEx1)

显然,上述例子就是把原例子中的JavaScript对象翻译成了R对象。注意,我们是用htmlwidgets中的JS()函数翻译了tooltip.fomatter函数。其他所有对象都可以自然地映射到R。

这个例子等价于

mat <- as.data.frame(rbind(
        c(11975,  5871, 8916, 2868),
        c( 1951, 10048, 2060, 6171),
        c( 8010, 16145, 8090, 8045),
        c( 1013,   990,  940, 6907)
      ))
names(mat) <- c("group1", "group2", "group3", "group4")
mat$name <- names(mat)

echartr(mat, x=name, y=c(group1, group2, group3, group4), type="chord", 
        subtype='ribbon + asc + descsub + hidelab + scaletext') %>%
        setTitle("测试数据", subtitle="From d3.js", pos=5)

4.2 修改Echarts对象Modify the Echarts Object

如果熟悉Echarts的数据结构,我们也可以用echartr创建Echarts对象,然后直接修改它,就如同修改其他S3 list一样。

下面是Echarts对象g的数据结构。g是通过命令echartr(mtcars, wt, mpg, factor(am, labels=c('Automatic', 'Mannual')))创建出来的。

enquote(g)
## quote(list(x = list(series = list(list(name = "Automatic", type = "scatter", 
##     data = list(list(3.215, 21.4), list(3.44, 18.7), list(3.46, 
##         18.1), list(3.57, 14.3), list(3.19, 24.4), list(3.15, 
##         22.8), list(3.44, 19.2), list(3.44, 17.8), list(4.07, 
##         16.4), list(3.73, 17.3), list(3.78, 15.2), list(5.25, 
##         10.4), list(5.424, 10.4), list(5.345, 14.7), list(2.465, 
##         21.5), list(3.52, 15.5), list(3.435, 15.2), list(3.84, 
##         13.3), list(3.845, 19.2))), list(name = "Manual", type = "scatter", 
##     data = list(list(2.62, 21), list(2.875, 21), list(2.32, 22.8), 
##         list(2.2, 32.4), list(1.615, 30.4), list(1.835, 33.9), 
##         list(1.935, 27.3), list(2.14, 26), list(1.513, 30.4), 
##         list(3.17, 15.8), list(2.77, 19.7), list(3.57, 15), list(
##             2.78, 21.4)))), legend = list(show = TRUE, data = list(
##     "Automatic", "Manual"), x = "left", y = "top", orient = "horizontal"), 
##     yAxis = list(list(type = "value", show = TRUE, position = "left", 
##         name = "mpg", nameLocation = "end", boundaryGap = c(0, 
##         0), scale = TRUE, axisLine = list(show = TRUE, onZero = FALSE), 
##         axisTick = list(show = FALSE), axisLabel = list(show = TRUE), 
##         splitLine = list(show = TRUE), splitArea = list(show = FALSE))), 
##     xAxis = list(list(type = "value", show = TRUE, position = "bottom", 
##         name = "wt", nameLocation = "end", boundaryGap = c(0, 
##         0), scale = TRUE, axisLine = list(show = TRUE, onZero = FALSE), 
##         axisTick = list(show = FALSE), axisLabel = list(show = TRUE), 
##         splitLine = list(show = TRUE), splitArea = list(show = FALSE))), 
##     tooltip = list(show = TRUE, trigger = "axis", axisPointer = list(
##         type = "cross", crossStyle = list(type = "dashed"), lineStyle = list(
##             type = "solid", width = 1), shadowStyle = list(color = "rgba(150,150,150,0.3)", 
##             width = "auto", type = "default")), textStyle = list(
##         color = "#fff"), formatter = "function (params) {\n    var i;\n    var text = params.value[0];\n    if (params.seriesName === null || params.seriesName === \"\"){\n        if (params.value.length > 1) {\n            for (i = 1; i < params.value.length; i++){\n                text += \" ,    \" + params.value[i];\n            }\n            return text;\n        } else {\n            return params.name + \" : \" + text;\n        }\n    } else {\n        if (params.value.length > 1) {\n            text = params.seriesName + \" :<br/>\" + text;\n            for (i = 1; i < params.value.length; i++) {\n                text += \" ,    \" + params.value[i];\n            }\n            return text;\n        } else {\n            return params.seriesName + \" :<br/>\"\n            + params.name + \" : \" + text;\n        }\n    }\n    }", 
##         islandFormatter = "{a} < br/>{b} : {c}", enterable = FALSE, 
##         showDelay = 20, hideDelay = 100, transitionDuration = 0.4, 
##         backgroundColor = "rgba(0,0,0,0.7)", borderColor = "#333", 
##         borderWidth = 0, borderRadius = 4), toolbox = list(show = TRUE, 
##         feature = list(mark = list(show = TRUE), dataZoom = list(
##             show = TRUE), dataView = list(show = TRUE, readOnly = FALSE), 
##             magicType = list(show = FALSE), restore = list(show = TRUE), 
##             saveAsImage = list(show = TRUE)), x = "right", y = "top", 
##         orient = "horizontal")), width = NULL, height = NULL, 
##     sizingPolicy = list(defaultWidth = NULL, defaultHeight = NULL, 
##         padding = NULL, viewer = list(defaultWidth = NULL, defaultHeight = NULL, 
##             padding = NULL, fill = TRUE, suppress = FALSE, paneHeight = NULL), 
##         browser = list(defaultWidth = NULL, defaultHeight = NULL, 
##             padding = NULL, fill = FALSE), knitr = list(defaultWidth = NULL, 
##             defaultHeight = NULL, figure = TRUE)), dependencies = NULL, 
##     elementId = NULL, preRenderHook = NULL, jsHooks = list()))

4.3 将Echarts对象保存为HTML

Echarts本质上是一个htmlwidgets对象。所以只要调用htmlwidgets包的saveWidget函数即可将其保存为HTML文件。

4.4 Shiny

也可以将Echarts用于Shiny。具体可参考renderEchart函数。

library(recharts)
library(shiny)
app = shinyApp(
  ui = fluidPage(eChartOutput('myChart')),
  server = function(input, output) {
    chart = echartr(data.frame(x = rnorm(100), y = rnorm(100)), x, y)
    output$myChart = renderEChart(chart)
  }
)

if (interactive()) print(app)

5 结语Finale

通常我们不希望R用户写那么长的选项列表。R用户一般更熟悉(也更常用到)诸如数据框、矩阵、表格等数据结构,理想状态下,我们希望用户只要传入一些熟悉的数据对象,recharts自动地配置出ECharts对象来。欢迎反馈您的体验和看法,也欢迎用Github pull requests贡献您的代码。