Skip to content

Commit 052ed77

Browse files
author
Larry Helgason
committed
v.1.6.7
with Echarts 6.0.0
1 parent 34ab93f commit 052ed77

23 files changed

+872
-407
lines changed

DESCRIPTION

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Package: echarty
22
Title: Minimal R/Shiny Interface to JavaScript Library 'ECharts'
3-
Date: 2025-01-15
4-
Version: 1.6.6
3+
Date: 2025-08-14
4+
Version: 1.6.7
55
Authors@R: c(
66
person(given= "Larry", family= "Helgason", role= c("aut", "cre"), email= "larry@helgasoft.com", comment="initial code from John Coene's library echarts4r")
77
)

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export(ec.init)
77
export(ec.inspect)
88
export(ec.paxis)
99
export(ec.plugjs)
10+
export(ec.registerMap)
1011
export(ec.theme)
1112
export(ec.upd)
1213
export(ec.util)

NEWS.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
# history of package _echarty_
22

3-
# echarty 1.6.6 latest in development
3+
# echarty 1.6.7 latest in development
44

5-
* more tests to increase coverage without disturbing CRAN submission
5+
* upgrade ECharts to v.6.0.0, built with R v.4.4.3.
6+
* new signature of _ec.util_: _cmd_ is now first, to avoid writing 'cmd=...'
7+
* improved _tabset_ in _ec.util_
8+
* new _ec.init_ parameter _iniOpts_ for initialization options
9+
* new helper _ec.registerMap_ for geoJSON and SVG maps
10+
* new _ec.data_ helper _'borders'_: get geoJSON region borders from data.frame
11+
* auto-add _load='world'_ if missing in series _map='world'_
12+
* auto-register _ecStat.transform_ when _load='ecStat'_ is set
613

7-
# echarty 1.6.5 on CRAN
14+
# echarty 1.6.6 on CRAN
815

916
* upgrade ECharts to v.5.6.0, built with R v.4.4.2.
1017
* auto-load 3D plugin when 3D attributes present (xAxis3D, bar3D, etc.).
@@ -15,6 +22,7 @@
1522
* add _event_ parameter in _ec.util_ for cmd='morph'.
1623
* integrate website with library using _pkgdown_.
1724
* moved _examples.R_ into 'demo' folder, _ec.examples_ is no longer a command.
25+
* more tests to increase coverage without disturbing CRAN submission
1826

1927
# echarty 1.6.4
2028

R/echarty.R

Lines changed: 100 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -47,18 +47,17 @@ noCoord <- c('polar','radar','singleAxis','parallelAxis','calendar')
4747
#'
4848
#' **Widget attributes** \cr
4949
#' Optional echarty widget attributes include: \cr
50-
#' * elementId - Id of the widget, default is NULL(auto-generated)
50+
#' * elementId - Id of the widget, default is NULL(auto-generated, stored as _echwid_ variable for JS)
5151
#' * load - name(s) of plugin(s) to load. A character vector or comma-delimited string. default NULL.
5252
#' * ask - prompt user before downloading plugins when _load_ is present, FALSE by default
5353
#' * js - single string or a vector with JavaScript expressions to evaluate.\cr
5454
#' single: exposed _chart_ object (most common)\cr
5555
#' vector: \verb{ }see code in \href{https://github.com/helgasoft/echarty/blob/main/demo/examples.R}{examples}\cr
56-
#' \verb{ }First expression evaluated before initialization, exposed object _window_ \cr
56+
#' \verb{ }First expression evaluated with exposed objects _window_ and _echarts_ \cr
5757
#' \verb{ }Second is evaluated with exposed object _opts_. \cr
58-
#' \verb{ }Third is evaluated with exposed _chart_ object after _opts_ set.
59-
#' * renderer - 'canvas'(default) or 'svg'
60-
#' * locale - 'EN'(default) or 'ZH'. Use predefined or custom \href{https://gist.github.com/helgasoft/0618c6537c45bfd9e86d3f9e1da497b8}{like so}.
61-
#' * useDirtyRect - enable dirty rectangle rendering or not, FALSE by default, see \href{https://echarts.apache.org/en/api.html#echarts.init}{here}\cr
58+
#' \verb{ }Third is evaluated with exposed _chart_ object after initialization with _opts_ already set.
59+
#' * iniOpts - a list of initialization options, see _opts_ in \href{https://echarts.apache.org/en/api.html#echarts.init}{echarts.init}\cr
60+
#' \verb{ }Defaults: renderer='canvas', locale='EN', useDirtyRect=FALSE\cr
6261
#'
6362
#' **Built-in plugins** \cr
6463
#' * leaflet - Leaflet maps with customizable tiles, see \href{https://github.com/gnijuohz/echarts-leaflet#readme}{source}\cr
@@ -100,8 +99,15 @@ noCoord <- c('polar','radar','singleAxis','parallelAxis','calendar')
10099
#' @return A widget to plot, or to save and expand with more features.
101100
#'
102101
#' @examples
103-
#' # basic scatter chart from a data.frame, using presets
102+
#' # basic scatter chart from a data.frame using presets
104103
#' cars |> ec.init()
104+
#'
105+
#' # custom inititlization options and theme
106+
#' cars |> ec.init(
107+
#' iniOpts= list(renderer= 'svg', width= '222px'),
108+
#' toolbox= list(feature= list(saveAsImage= list()))
109+
#' ) |> ec.theme('mine', code= # theme code JSON
110+
#' '{"color": ["green"], "backgroundColor": "lemonchiffon"}' )
105111
#'
106112
#' # grouping, tooltips, formatting
107113
#' iris |> dplyr::group_by(Species) |>
@@ -134,28 +140,27 @@ ec.init <- function( df= NULL, preset= TRUE, ctype= 'scatter', ...,
134140
elementId <- if (is.null(opt1$elementId)) NULL else opt1$elementId
135141
js <- if (is.null(opt1$js)) NULL else opt1$js
136142
ask <- if (is.null(opt1$ask)) FALSE else opt1$ask
137-
renderer <- if (is.null(opt1$renderer)) 'canvas' else tolower(opt1$renderer)
138-
locale <- if (is.null(opt1$locale)) 'EN' else toupper(opt1$locale)
139-
useDirtyRect <- if (is.null(opt1$useDirtyRect)) FALSE else opt1$useDirtyRect
143+
iniOpts <- if (!is.null(opt1$iniOpts)) opt1$iniOpts else list()
144+
# for backward compatibility:
145+
if (is.null(iniOpts$renderer)) iniOpts$renderer <- if (is.null(opt1$renderer)) 'canvas' else tolower(opt1$renderer)
146+
if (is.null(iniOpts$locale)) iniOpts$locale <- if (is.null(opt1$locale)) 'EN' else toupper(opt1$locale)
147+
if (is.null(iniOpts$useDirtyRect)) iniOpts$useDirtyRect <- if (is.null(opt1$useDirtyRect)) FALSE else opt1$useDirtyRect
148+
140149
xtKey <- if (is.null(opt1$xtKey)) 'XkeyX' else opt1$xtKey
141150
# allow debug feedback thru cat() in JS and R code:
142151
dbg <- if (is.null(opt1$dbg)) FALSE else opt1$dbg
143152
# remove the above attributes since they are not valid ECharts options
144-
opt1$ask <- opt1$js <- opt1$renderer <- opt1$locale <- NULL
145-
opt1$useDirtyRect <- opt1$elementId <- opt1$xtKey <- opt1$dbg <- NULL
153+
opt1$renderer <- opt1$locale <- opt1$useDirtyRect <- opt1$iniOpts <- NULL
154+
opt1$ask <- opt1$js <- opt1$elementId <- opt1$xtKey <- opt1$dbg <- NULL
146155
axis2d <- c('pictorialBar','candlestick','boxplot','scatterGL') #'custom',
147156
isCrosstalk <- FALSE; deps <- NULL
148157

149158
# forward widget options using x
150159
x <- list(
151160
theme = '',
152-
draw = TRUE,
153-
renderer = renderer,
154-
locale = locale,
155-
useDirtyRect = useDirtyRect,
161+
iniOpts = iniOpts,
156162
jcode = js, dbg = dbg,
157163
opts = opt1
158-
# settings= list( crosstalk_key= key, crosstalk_group= group )
159164
)
160165

161166
doType <- function(idx, axx) {
@@ -488,14 +493,39 @@ ec.init <- function( df= NULL, preset= TRUE, ctype= 'scatter', ...,
488493
opt1 <- wt$x$opts
489494
load <- opt1$load; wt$x$opts$load <- NULL
490495
if (length(load)==1 && grepl(',', load, fixed=TRUE))
491-
load <- unlist(strsplit(load, ','))
496+
load <- unlist(strsplit(load, ',')) # make 'load' a vector
497+
# series.param has been merged into series
498+
if (dbg) cat('\nseries=',unlist(opt1$series))
499+
492500
# autoload 3D
493501
cnd1 <- any(c('xAxis3D','yAxis3D','zAxis3D','grid3D','globe','geo3D') %in% names(opt1))
494-
styp <- ctype
495-
if (!is.null(series.param) && !is.null(series.param$type))
496-
styp <- series.param$type
497-
cnd2 <- any(endsWith(styp, c('3D','GL')))
502+
cnd2 <- !is.null(opt1$series)
503+
if (cnd2)
504+
cnd2 <- length( opt1$series[sapply(opt1$series,
505+
\(x) any(endsWith(x$type, c('3D','GL'))) )] ) >0
498506
if ((cnd1 || cnd2) && !'3D' %in% load) load <- c(load, '3D')
507+
508+
# autoload 'world' - for geo OR series
509+
cnd1 <- !is.null(opt1$geo); cnd2 <- FALSE
510+
if (cnd1 && !is.null(opt1$geo$map))
511+
cnd2 <- opt1$geo$map=='world'
512+
if (!cnd1 || !cnd2) { #series
513+
cnd1 <- !is.null(opt1$series); cnd2 <- FALSE
514+
if (cnd1) {
515+
# search series for type=='map', map=='world'
516+
tmc <- 0
517+
cnd2 <- opt1$series[sapply(opt1$series, \(x) {
518+
if ('map' %in% names(x)) {
519+
if (!is.null(x$type) && x$type=='map') tmc <<- tmc+1
520+
x$map=='world' } else FALSE
521+
})]
522+
cnd2 <- length(cnd2)==1
523+
if (cnd2) stopifnot("type=='map' missing in series"= tmc==1)
524+
}
525+
}
526+
if (cnd1 && cnd2) {
527+
if (!'world' %in% load) load <- c(load, 'world')
528+
}
499529

500530
path <- system.file('js', package= 'echarty')
501531
dep <- NULL
@@ -574,6 +604,18 @@ ec.init <- function( df= NULL, preset= TRUE, ctype= 'scatter', ...,
574604
wt$dependencies <- append(wt$dependencies, list(dep))
575605
}
576606
if ('ecStat' %in% load) {
607+
# add registration if missing in wt$x$jcode
608+
jreg <- paste(lapply(list('histogram','regression','clustering','statistics'),
609+
\(x) { paste0('echarts.registerTransform(ecStat.transform.',x,')') }), collapse=';')
610+
if (!is.null(js)) {
611+
if (!grepl("registerTransform", js, fixed=TRUE)[[1]]) {
612+
if (length(js)==1) js <- c(jreg,'',js)
613+
else js[[1]] <- paste(js[[1]], jreg)
614+
wt$x$jcode <- js
615+
}
616+
} else
617+
wt$x$jcode <- c(jreg,'','')
618+
577619
dep <- htmltools::htmlDependency(
578620
name = 'ecStat', version = '1.0.0',
579621
src = c(file = path), script= 'ecStat.min.js')
@@ -585,14 +627,17 @@ ec.init <- function( df= NULL, preset= TRUE, ctype= 'scatter', ...,
585627
plf <- read.csv(system.file('plugins.csv', package='echarty'), header=TRUE, stringsAsFactors=FALSE)
586628
if ('3D' %in% load) {
587629
if (preset) { # replace 2D presets with 3D
588-
isGL <- any(unlist(lapply(opt1$series, \(k){ endsWith(k$type, 'GL') }))) # all GL are 2D
589-
if (!isGL) isGL <- endsWith(styp, 'GL')
630+
isGL <- any(unlist(lapply(opt1$series, \(k){ endsWith(k$type, 'GL') }))) # GL is 2D
590631
isMap3d <- !is.null(opt1$globe) || !is.null(opt1$geo3D)
591632
if (isMap3d) isGL <- FALSE
592-
if (!isGL) {
633+
if (!isGL) {
634+
# replace 2D types with 3D if any
635+
wt$x$opts$series <- lapply(opt1$series, \(ss) {
636+
if (ss$type %in% c('scatter','bar','line')) ss$type <- paste0(ss$type,'3D')
637+
ss
638+
})
593639
# check for series types ending in 3D or GL
594-
stypes <- ifelse(!is.null(series.param), styp,
595-
unlist(lapply(opt1$series, \(k){k$type})) )
640+
stypes <- unlist(lapply(wt$x$opts$series, \(k){k$type}))
596641
stypes <- stypes[stypes!='surface']
597642
if (!is.null(stypes)) stopifnot("Non-3D series type detected"= all(endsWith(stypes, '3D')) )
598643
if (!isMap3d) {
@@ -766,7 +811,7 @@ ec.init <- function( df= NULL, preset= TRUE, ctype= 'scatter', ...,
766811
} else
767812
wt$x$opts$timeline <- .merlis(wt$x$opts$timeline, list(data=steps, axisType='category'))
768813

769-
return(wt)
814+
wt
770815
}
771816

772817

@@ -783,18 +828,20 @@ ec.init <- function( df= NULL, preset= TRUE, ctype= 'scatter', ...,
783828
#' @examples
784829
#' library(dplyr)
785830
#' df <- data.frame(x= 1:30, y= runif(30, 5, 10), cat= sample(LETTERS[1:3],size=30,replace=TRUE)) |>
786-
#' mutate(lwr= y-runif(30, 1, 3), upr= y+runif(30, 2, 4))
831+
#' mutate(lwr= y-runif(30, 1, 3), upr= y+runif(30, 2, 4))
787832
#' band.df <- df |> group_by(cat) |> group_split()
833+
#' sband <- list()
834+
#' for(ii in 1:length(band.df)) # build all bands
835+
#' sband <- append(sband,
836+
#' ecr.band(band.df[[ii]], 'lwr', 'upr', type='stack', smooth=FALSE,
837+
#' name= unique(band.df[[ii]]$cat), areaStyle= list(color=c('blue','green','yellow')[ii]))
838+
#' )
788839
#'
789840
#' df |> group_by(cat) |>
790841
#' ec.init(load='custom', ctype='line',
791842
#' xAxis=list(data=c(0,unique(df$x)), boundaryGap=FALSE) ) |>
792-
#' ec.upd({
793-
#' for(ii in 1:length(band.df)) # add bands to their respective groups
794-
#' series <- append(series,
795-
#' ecr.band(band.df[[ii]], 'lwr', 'upr', type='stack', smooth=FALSE,
796-
#' name= unique(band.df[[ii]]$cat), areaStyle= list(color=c('blue','green','yellow')[ii])) )
797-
#' })
843+
#' ec.upd({ series <- append(series, sband) })
844+
#'
798845
#' @export
799846
ec.upd <- function(wt, ...) {
800847
stopifnot('ec.upd: expecting wt as echarty widget'= inherits(wt, 'echarty'))
@@ -1211,7 +1258,7 @@ ec.plugjs <- function(wt=NULL, source=NULL, ask=FALSE) {
12111258
stopifnot('ec.plugjs: expecting source as URL or file://'=
12121259
startsWith(source, 'http') || startsWith(source, 'file://'))
12131260
if (!.valid.url(source)) { # CRAN does not like stopifnot errors
1214-
wt$x$opts$title <- list(text='ERROR ec.plugjs: source is invalid!')
1261+
wt$x$opts <- list(title= list(text= paste0('ec.plugjs: source "',source,'" is invalid!')))
12151262
return(wt)
12161263
}
12171264
fname <- basename(source)
@@ -1254,19 +1301,21 @@ ec.plugjs <- function(wt=NULL, source=NULL, ask=FALSE) {
12541301

12551302
# convert from R to JS numbering
12561303
.renumber <- function(opa) {
1257-
1304+
doEncode <- function(el) {
1305+
for(i in 1:length(el$encode)) {
1306+
if (!is.numeric(el$encode[[i]])) next
1307+
el$encode[[i]] <- el$encode[[i]] -1
1308+
}
1309+
el
1310+
}
12581311
r2jsEncode <- function(ss) {
12591312

1260-
if (any(names(ss)=='encode')) {
1261-
for(i in 1:length(ss$encode)) {
1262-
if (!is.numeric(ss$encode[[i]])) next
1263-
ss$encode[[i]] <- ss$encode[[i]] -1
1264-
}
1265-
}
1266-
if (!ss$type %in% noAxis) {
1313+
if (any(names(ss)=='encode')) ss <- doEncode(ss)
1314+
1315+
#if (!is.null(ss$type) && !ss$type %in% noAxis) {
12671316
if (!is.null(ss$xAxisIndex)) ss$xAxisIndex <- ss$xAxisIndex -1
12681317
if (!is.null(ss$yAxisIndex)) ss$yAxisIndex <- ss$yAxisIndex -1
1269-
}
1318+
#}
12701319
if (!is.null(ss$datasetIndex)) ss$datasetIndex <- ss$datasetIndex -1
12711320
if (!is.null(ss$geoIndex)) ss$geoIndex <- ss$geoIndex -1
12721321
if (!is.null(ss$polarIndex)) ss$polarIndex <- ss$polarIndex -1
@@ -1275,8 +1324,11 @@ ec.plugjs <- function(wt=NULL, source=NULL, ask=FALSE) {
12751324
ss
12761325
}
12771326

1278-
if (!is.null(opa$series))
1279-
opa$series <- lapply(opa$series, r2jsEncode)
1327+
if (!is.null(opa$series)) opa$series <- lapply(opa$series, r2jsEncode)
1328+
if (!is.null(opa$dataZoom)) {
1329+
if (all(sapply(opa$dataZoom, is.list))) opa$dataZoom <- lapply(opa$dataZoom, r2jsEncode)
1330+
else opa$dataZoom <- r2jsEncode(opa$dataZoom)
1331+
}
12801332

12811333
decro <- function(x) {
12821334
if (!is.null(x$dimension) && is.numeric(x$dimension)) x$dimension <- x$dimension -1
@@ -1317,7 +1369,7 @@ ec.plugjs <- function(wt=NULL, source=NULL, ask=FALSE) {
13171369
# validate URL (https:// or file://)
13181370
.valid.url <- function(durl, tsec=2){
13191371
con <- url(durl)
1320-
check <- suppressWarnings(try(open.connection(con,open="rt",timeout=tsec),silent=TRUE)[1])
1372+
check <- suppressWarnings(try(open.connection(con, open="rt", timeout=tsec),silent=TRUE)[1])
13211373
suppressWarnings(try(close.connection(con),silent=TRUE))
13221374
ifelse(is.null(check),TRUE,FALSE)
13231375
}

0 commit comments

Comments
 (0)