Skip to content

Commit b89217f

Browse files
author
Larry Helgason
committed
renderItem
1 parent eff2b7a commit b89217f

File tree

10 files changed

+162
-76
lines changed

10 files changed

+162
-76
lines changed

DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Package: echarty
22
Title: Minimal R/Shiny Interface to JavaScript Library 'ECharts'
3-
Date: 2026-01-12
3+
Date: 2026-01-17
44
Version: 1.7.2
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")

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
* added _matrixIndex_, _calendarIndex_ and other indexes in .renumber
66
* fixed some _globe,3D_ presets
7+
* new v.6 custom series loaded by _renderItem_ name only (no load/ask needed)
78

89
# echarty 1.7.1 on CRAN
910

R/echarty.R

Lines changed: 66 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ lenv <- new.env(parent = emptyenv())
1111
lenv$coNames <- ''
1212
# assign('aa', 11, envir=.ecv.colnames); get('aa', envir=.ecv.colnames)
1313
noAxisXY <- c('radar','parallel','themeRiver','map','gauge','pie','funnel','polar','chord',
14-
'sunburst','tree','treemap','sankey','lines', 'liquidFill','wordCloud') # series
14+
'sunburst','tree','treemap','sankey','lines', 'liquidFill','wordCloud','segmentedDoughnut') # series
1515
noCoord <- c('polar','radar','singleAxis','parallelAxis','calendar')
16+
plf <- read.csv(system.file('plugins.csv', package='echarty'), header=TRUE, stringsAsFactors=FALSE)
17+
renderCustom <- setNames(as.list(plf[[2]]), plf[[1]])
1618
# using list(show=TRUE) or list(list()) is to create empty object{} in JS
1719

1820
#' Initialize a chart
@@ -72,15 +74,16 @@ noCoord <- c('polar','radar','singleAxis','parallelAxis','calendar')
7274
#' * world - world map with country boundaries, see \href{https://github.com/apache/echarts/tree/master/test/data/map/js}{source} \cr
7375
#' * lottie - support for \href{https://lottiefiles.com}{lotties} \cr
7476
#' * ecStat - statistical tools, see\href{https://github.com/ecomfe/echarts-stat}{echarts-stat}\cr
75-
#' * custom - renderers for [ecr.band] and [ecr.ebars] \cr
77+
#' * custom - renderers for echarty plugins like [ecr.band] and [ecr.ebars] \cr
7678
#'
7779
#' **Plugins with one-time installation** \cr
7880
#' * 3D - support for 3D charts and WebGL acceleration, see \href{https://github.com/ecomfe/echarts-gl}{source} and \href{https://echarts.apache.org/en/option-gl.html#series}{docs} \cr
7981
#' \verb{ } This plugin is auto-loaded when 3D/GL axes/series are detected.\cr
80-
#' * liquid - liquid fill, see \href{https://github.com/ecomfe/echarts-liquidfill}{source} \cr
8182
#' * gmodular - graph modularity, see \href{https://github.com/ecomfe/echarts-graph-modularity}{source} \cr
83+
#' * liquid - liquid fill, see \href{https://github.com/ecomfe/echarts-liquidfill}{source} \cr
8284
#' * wordcloud - cloud of words, see \href{https://github.com/ecomfe/echarts-wordcloud}{source} \cr
83-
#' or install your own third-party plugins.\cr
85+
#' Note: the last three are being moved to the \href{https://github.com/apache/echarts-custom-series}{official custom series}.\cr
86+
#' OR install your own third-party plugins like _confetti_, see example below.\cr
8487
#'
8588
#' **Crosstalk** \cr
8689
#' Parameter _df_ should be of type \link[crosstalk]{SharedData}, see \href{https://helgasoft.github.io/echarty/articles/gallery.html#crosstalk-2d}{more info}.\cr
@@ -116,8 +119,8 @@ noCoord <- c('polar','radar','singleAxis','parallelAxis','calendar')
116119
#' # custom inititlization options and theme
117120
#' myth <- '{"color": ["green"], "backgroundColor": "lemonchiffon"}'
118121
#' ec.init( cars,
119-
#' theme= jsonlite::fromJSON(myth),
120122
#' iniOpts= list(renderer= 'svg', width= '222px'),
123+
#' theme= jsonlite::fromJSON(myth),
121124
#' toolbox= list(feature= list(saveAsImage= list()))
122125
#' )
123126
#'
@@ -140,6 +143,19 @@ noCoord <- c('polar','radar','singleAxis','parallelAxis','calendar')
140143
#' series.param= list(type='gauge', max=5)
141144
#' )
142145
#'
146+
#' ec.init(
147+
#' series.param= list(
148+
#' renderItem= 'segmentedDoughnut', # v.6 from https://github.com/apache/echarts-custom-series
149+
#' itemPayload= list(segmentCount= 8, label= list(show=T, formatter= '{c}/{b}', fontSize=35) ),
150+
#' data= list(5) )
151+
#' )
152+
#'
153+
#' ec.init(cars, js= 'confetti();', # js code executes on init
154+
#' load= 'https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.4/dist/confetti.browser.min.js',
155+
#' ask= 'loadRemote',
156+
#' on= list(list(event= 'click', handler= ec.clmn('() => confetti()')) )
157+
#' )
158+
#'
143159
#' @importFrom htmlwidgets createWidget sizingPolicy getDependency JS shinyWidgetOutput shinyRenderWidget
144160
#' @importFrom utils read.csv modifyList
145161
#' @import dplyr
@@ -154,7 +170,7 @@ ec.init <- function( df= NULL, preset= TRUE, ..., #ctype= 'scatter',
154170
# treacherous R does "partial matching of argument names" (like a bug):
155171
# if 'series.param' is before '...' and 'series' is added, the latter is ignored!
156172
elementId <- opt1$elementId; js <- opt1$js
157-
ask <- if (is.null(opt1$ask)) FALSE else opt1$ask
173+
ask <- ifelse(!is.null(opt1$ask), opt1$ask, FALSE)
158174
ctype <- if (is.null(opt1$ctype)) 'scatter' else opt1$ctype
159175
iniOpts <- if (!is.null(opt1$iniOpts)) opt1$iniOpts else list()
160176
# set defaults + backward compat:
@@ -165,7 +181,6 @@ ec.init <- function( df= NULL, preset= TRUE, ..., #ctype= 'scatter',
165181
xtKey <- if (is.null(opt1$xtKey)) 'XkeyX' else opt1$xtKey
166182
# allow debug feedback thru cat() in R & JS code
167183
dbg <- if (is.null(opt1$dbg)) FALSE else opt1$dbg
168-
if (dbg) cat('\n coln=', lenv$coNames)
169184
# remove the above attributes since they are not valid ECharts options
170185
for (ii in c('renderer','locale','useDirtyRect','iniOpts','ask','js',
171186
'elementId','xtKey','dbg','ctype')) opt1[ii] <- NULL
@@ -471,7 +486,7 @@ ec.init <- function( df= NULL, preset= TRUE, ..., #ctype= 'scatter',
471486
tl.series$type <- ctype
472487
}
473488

474-
if ('series' %in% names(x$opts)) {
489+
if ('series' %in% namop) {
475490
if (!is.null(ctype)) # all series, not 1st only
476491
x$opts$series <- lapply(x$opts$series, function(ss) {
477492
if (is.null(ss$type)) ss$type <- ctype
@@ -492,6 +507,24 @@ ec.init <- function( df= NULL, preset= TRUE, ..., #ctype= 'scatter',
492507
}
493508
axad <- 1; isSname <- FALSE
494509
x$opts$series <- lapply(x$opts$series, function(ss) {
510+
# renderItem helper
511+
if (!is.null(ss$renderItem) && inherits(ss$renderItem, "character")) {
512+
if (startsWith(ss$renderItem, 'ri'))
513+
ss$renderItem <- htmlwidgets::JS(ss$renderItem)
514+
else { # new ECharts 6 custom series want a character string
515+
bask <- (ask==FALSE) | (ask=='loadRemote')
516+
if (bask && !is.null(renderCustom[ss$renderItem])) {
517+
ask <<- 'loadRemote'
518+
x$opts$load <<- c(x$opts$load, renderCustom[[ss$renderItem]])
519+
}
520+
if (ss$renderItem %in% c('segmentedDoughnut','liquidFill','wordCloud'))
521+
ss$coordinateSystem <- 'none'
522+
if (!any(grepl('d3.min.js', x$opts$load)) && ss$renderItem=='contour')
523+
x$opts$load <<- c('https://cdn.jsdelivr.net/npm/d3@latest/dist/d3.min.js', x$opts$load)
524+
}
525+
if (!is.null(ss$type)) ss$type <- 'custom'
526+
}
527+
495528
tmp <- xyNamesCS(ss)
496529
if (!is.null(tmp$c)) ss$coordinateSystem <- tmp$c
497530

@@ -510,10 +543,6 @@ ec.init <- function( df= NULL, preset= TRUE, ..., #ctype= 'scatter',
510543
}
511544
if (!is.null(ss$name)) isSname <<- TRUE
512545

513-
# renderItem helper only for our custom functions ri*
514-
# new ECharts custom series, like segmentedDoughnut, want a character string
515-
if (!is.null(ss$renderItem) && inherits(ss$renderItem,"character") && startsWith(ss$renderItem, 'ri'))
516-
ss$renderItem <- htmlwidgets::JS(ss$renderItem)
517546
ss
518547
})
519548
if (any(c('geo','leaflet','globe', noCoord) %in% namop)) axad <- 0 # was axad-1
@@ -542,7 +571,7 @@ ec.init <- function( df= NULL, preset= TRUE, ..., #ctype= 'scatter',
542571
# set X,Y axes type & name from df OR from series.data
543572

544573
cnms <- NULL
545-
ena <- c('x','y') # for polar > c('radius','angle') #,'lng','lat')
574+
ena <- c('x','y') # TODO: for polar= c('radius','angle') map= c('lng','lat')
546575
coln <- colp <- ctyp <- list() # column names, positions, types
547576
for(xy in ena) { coln[xy] <- ctyp[xy] <- colp[xy] <- NA }
548577

@@ -556,9 +585,9 @@ ec.init <- function( df= NULL, preset= TRUE, ..., #ctype= 'scatter',
556585
!paste0(s1$type,s1$layout) %in% c('graphforce','graphcircular') &&
557586
!slb && !paste0('',s1$coordinateSystem) %in% c('leaflet','geo') &&
558587
!any(noCoord %in% names(x$opts))
559-
if (isAxes) { # can search for XY axes names/type
588+
if (isAxes) { # search for single X/Y axis name and type
560589
colp[1] <- colX[1]; colp[2] <- colY[1];
561-
if (!is.null(s1$encode)) { # chk $data then $dset
590+
if (!is.null(s1$encode)) {
562591
for(xy in ena) {
563592
exy <- s1$encode[[xy]]
564593
if (!is.null(exy) && length(exy) <2 ) { # exclude multi-values like candlestick
@@ -567,7 +596,7 @@ ec.init <- function( df= NULL, preset= TRUE, ..., #ctype= 'scatter',
567596
}
568597
}
569598
}
570-
599+
# check first $data then $dset
571600
if (exists('sedval')) { # encode$data$value takes precedence
572601
i <- 1
573602
for(xy in ena) {
@@ -576,7 +605,8 @@ ec.init <- function( df= NULL, preset= TRUE, ..., #ctype= 'scatter',
576605
}
577606
else if ('data' %in% names(s1)) {
578607
# chk s1$data for type (and maybe name) list(value=) or list(c()) or mixed
579-
# verify only first row in list-of-lists # non-named but can have names in s1$dimens
608+
# verify only first row in list-of-lists
609+
# TODO: s1$data could be non-named but have names in s1$dimensions
580610
d1 <- if (is.list(s1$data) && is.list(s1$data[[1]])) s1$data[[1]] else s1$data
581611
if (!is.null(names(d1))) {
582612
cnms <- names(d1)
@@ -589,11 +619,13 @@ ec.init <- function( df= NULL, preset= TRUE, ..., #ctype= 'scatter',
589619
}
590620
}
591621
else { # like data=c(1,2..)
592-
if (!is.na(coln[[xy]]) && coln[[xy]] %in% cnms) ctyp[xy] <- tail(class(d1[[coln[[xy]]]]), 1)
622+
if (!is.na(coln[[xy]]) && coln[[xy]] %in% cnms) ctyp[xy] <- tail(class(d1[[ coln[[xy]] ]]), 1)
593623
else if (!is.na(colp[[xy]])) ctyp[xy] <- tail(class(d1[[colp[[xy]]]]), 1) # numeric array
594624
}
595625
}
596-
else if (!is.null(x$opts$dataset)) { # dataset # TODO ignore if {nam=...} format ?
626+
else if (!is.null(x$opts$dataset)) { # dataset
627+
# TODO: ignore when format is [{name=...},..] ?
628+
# TODO: types of multiple xAxis[] (when no series$encode)
597629
dset <- x$opts$dataset[[1]]
598630
r1 <- dset$source[[1]]
599631
if (!is.null(dset$dimensions)) cnms <- dset$dimensions
@@ -602,9 +634,12 @@ ec.init <- function( df= NULL, preset= TRUE, ..., #ctype= 'scatter',
602634
cnms <- unlist(r1) # header
603635
r1 <- dset$source[[2]]
604636
}
637+
# for(xy in ena) {
638+
# if (!is.na(coln[xy]) && coln[xy] %in% cnms) colp[xy] <- which(cnms==coln[[xy]]) # name to pos
639+
# if (!is.na(colp[xy])) ctyp[xy] <- tail(class(r1[[colp[[xy]]]]), 1) # numeric array
640+
# }
605641
for(xy in ena) {
606-
if (!is.na(coln[xy]) && coln[xy] %in% cnms) colp[xy] <- which(cnms==coln[[xy]]) # name to pos
607-
if (!is.na(colp[xy])) ctyp[xy] <- tail(class(r1[[colp[[xy]]]]), 1) # numeric array
642+
if (!is.na(colp[xy])) ctyp[xy] <- tail(class(r1[[ colp[[xy]] ]]), 1)
608643
}
609644
}
610645

@@ -627,7 +662,8 @@ ec.init <- function( df= NULL, preset= TRUE, ..., #ctype= 'scatter',
627662
} # end isAxes
628663

629664
} # end preset
630-
665+
if (dbg) cat('\n cnames=', lenv$coNames)
666+
631667
x$opts <- .renumber(x$opts)
632668

633669
# ------------- create widget ----------------
@@ -789,8 +825,8 @@ ec.init <- function( df= NULL, preset= TRUE, ..., #ctype= 'scatter',
789825
}
790826

791827
# Plugins implemented as dynamic load on-demand
792-
if (any(load %in% c('3D','liquid','gmodular','wordcloud'))) {
793-
plf <- read.csv(system.file('plugins.csv', package='echarty'), header=TRUE, stringsAsFactors=FALSE)
828+
if (any(load %in% c('3D','gmodular','liquid','wordcloud'))) {
829+
#plf <- read.csv(system.file('plugins.csv', package='echarty'), header=TRUE, stringsAsFactors=FALSE)
794830
if ('3D' %in% load) {
795831
isGL <- any(unlist(lapply(opt1$series, \(k){ endsWith(k$type, 'GL') }))) # GL is 2D
796832
isMap3d <- !is.null(opt1$globe) || !is.null(opt1$geo3D)
@@ -828,13 +864,13 @@ ec.init <- function( df= NULL, preset= TRUE, ..., #ctype= 'scatter',
828864
}
829865
wt <- ec.plugjs(wt, plf[plf$name=='3D',]$url, ask)
830866
}
831-
if ('liquid' %in% load) wt <- ec.plugjs(wt, plf[plf$name=='liquid',]$url, ask)
832-
if ('gmodular' %in% load) wt <- ec.plugjs(wt, plf[plf$name=='gmodular',]$url, ask)
833-
if ('wordcloud' %in% load) wt <- ec.plugjs(wt, plf[plf$name=='wordcloud',]$url, ask)
867+
for(nn in c('gmodular','liquid','wordcloud')) {
868+
if (nn %in% load) wt <- ec.plugjs(wt, plf[plf$name==nn,]$url, ask) }
869+
#if ('liquid' %in% load) wt <- ec.plugjs(wt, plf[plf$name=='liquid',]$url, ask)
870+
#if ('wordcloud' %in% load) wt <- ec.plugjs(wt, plf[plf$name=='wordcloud',]$url, ask)
834871
}
835872
# load unknown plugins
836-
unk <- load[! load %in% c('leaflet','custom','world','lottie','ecStat',
837-
'3D','liquid','gmodular','wordcloud')]
873+
unk <- load[! load %in% c('leaflet','custom','world','lottie','ecStat','3D','gmodular','liquid','wordcloud')]
838874
if (length(unk)>0) {
839875
for(pg in unk)
840876
wt <- ec.plugjs(wt, pg, ask)
@@ -1450,7 +1486,6 @@ ec.plugjs <- function(wt=NULL, source=NULL, ask=FALSE) {
14501486
)
14511487
}
14521488
else { # download it
1453-
#if (ask=='loadRemote') ask <- FALSE
14541489
path <- system.file('js', package= 'echarty')
14551490

14561491
ffull <- paste0(path,'/',fname)
@@ -1498,6 +1533,7 @@ ec.plugjs <- function(wt=NULL, source=NULL, ask=FALSE) {
14981533
x
14991534
}
15001535
doEncode <- function(el) { # for tooltip, seriesName, itemId, itemName, itemGroupId, itemChildGroupId
1536+
# should be defined as vector, not list; ex: tooltip= c(2,3), not tooltip= list(2,3)
15011537
for(i in seq_along(el$encode)) {
15021538
if (!is.numeric(el$encode[[i]])) next
15031539
el$encode[[i]] <- el$encode[[i]] -1

R/util.R

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -287,10 +287,12 @@ ec.util <- function(cmd='sf.series', ..., js=NULL, event='click') {
287287
pat <- ifelse (is.null(opts$shp), '*.shp', paste0(opts$shp,'.shp'))
288288
tmp <- list.files(path= zfldr, pattern= pat, full.names=TRUE)
289289
#on.exit(unlink(zfldr, recursive=TRUE), add=TRUE) # cannot cleanup: file read later
290-
if (length(tmp)==0)
291-
out <- 'ERROR ec.util: unzipped file not found'
292-
else
293-
out <- tmp[1]
290+
291+
# if (length(tmp)==0)
292+
# out <- 'ERROR ec.util: unzipped file not found'
293+
# else
294+
# out <- tmp[1]
295+
out <- ifelse (length(tmp)==0, 'ERROR ec.util: unzipped file not found', tmp[1])
294296
}
295297
},
296298

@@ -1068,7 +1070,7 @@ return template.replace(/%@|%L@|%LR@|%R@|%R2@|%M@/g, (m) => {
10681070
warning("ec.clmn: colnames missing.
10691071
Use ec.clmn after ec.data and/or inside ec.init(df).
10701072
Otherwise use column indexes instead of names.", call.=FALSE)
1071-
return('col names?')
1073+
return('column names?')
10721074
}
10731075
# stopifnot("ec.clmn: colnames missing.
10741076
# Use ec.clmn after ec.data and/or inside ec.init(df).

inst/plugins.csv

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
name,url
22
3D,https://cdn.jsdelivr.net/npm/echarts-gl@2.0.9/dist/echarts-gl.min.js
3-
liquid,https://cdn.jsdelivr.net/npm/echarts-liquidfill@latest/dist/echarts-liquidfill.min.js
43
gmodular,https://cdn.jsdelivr.net/npm/echarts-graph-modularity@latest/dist/echarts-graph-modularity.min.js
4+
liquid,https://cdn.jsdelivr.net/npm/echarts-liquidfill@latest/dist/echarts-liquidfill.min.js
55
wordcloud,https://cdn.jsdelivr.net/npm/echarts-wordcloud@latest/dist/echarts-wordcloud.min.js
6+
segmentedDoughnut,https://cdn.jsdelivr.net/gh/apache/echarts-custom-series@main/custom-series/segmentedDoughnut/dist/segmented-doughnut.auto.min.js
7+
lineRange,https://cdn.jsdelivr.net/gh/apache/echarts-custom-series@main/custom-series/lineRange/dist/line-range.auto.js
8+
barRange,https://cdn.jsdelivr.net/gh/apache/echarts-custom-series@main/custom-series/barRange/dist/bar-range.auto.js
9+
contour,https://cdn.jsdelivr.net/gh/apache/echarts-custom-series@main/custom-series/contour/dist/contour.auto.js
10+
stage,https://cdn.jsdelivr.net/gh/apache/echarts-custom-series@main/custom-series/stage/dist/stage.auto.js
11+
violin,https://cdn.jsdelivr.net/gh/apache/echarts-custom-series@main/custom-series/violin/dist/violin.auto.js
12+
liquidFill,https://cdn.jsdelivr.net/gh/apache/echarts-custom-series@main/custom-series/liquidFill/dist/liquid-fill.auto.js
13+
wordCloud,https://cdn.jsdelivr.net/gh/apache/echarts-custom-series@main/custom-series/wordCloud/dist/word-cloud.auto.js

man/ec.init.Rd

Lines changed: 18 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/test-ec.clmn.R

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,5 +107,10 @@ test_that("ec.clmn with sprintf, column indexes and names", {
107107
) )
108108
expect_s3_class(p$x$on[[1]]$handler, 'JS_EVAL')
109109
expect_s3_class(p$x$on[[2]]$handler, 'JS_EVAL') # helper in ec.init
110+
111+
#expect_warning( ec.init(title= list(text=ec.clmn('templ','colname1'))) ) # lenv$coNames missing
112+
p <- ec.init(title= list(text=ec.clmn('templ','colname1'))) # lenv$coNames missing
113+
# expect_equal(p$x$opts$title$text, 'column names?')
114+
expect_true(grepl('[`colname1`]', p$x$opts$title$text, fixed =TRUE))
110115
})
111116

tests/testthat/test-ec.util.R

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ test_that("shapefile LINES from ZIP", {
3737
if (isCovr) { # creates a subfolder in temp folder
3838
library(sf)
3939
fname <- ec.util(cmd= 'sf.unzip',
40+
shp= 'railways', # set name: unzip folder can have other .shp files
4041
url= 'https://helgasoft.github.io/echarty/test/sl.shape.railways.zip')
4142
nc <- as.data.frame(st_read(fname, quiet=TRUE))
4243
p <- ec.init(load= 'leaflet',
@@ -55,7 +56,7 @@ test_that("shapefile LINES from ZIP", {
5556
expect_equal(p$x$opts$series[[6]]$lineStyle$color, 'red')
5657

5758
}
58-
else expect_false(interactive()) # bypass
59+
else expect_false(interactive()) # bypass in CRAN
5960
})
6061

6162
test_that("shapefile LINESTRING and MULTILINESTRING", {

0 commit comments

Comments
 (0)