Skip to content

Commit ad56644

Browse files
author
Larry Helgason
committed
1.7.2
1 parent e37e19b commit ad56644

File tree

15 files changed

+275
-108
lines changed

15 files changed

+275
-108
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-10-27
4-
Version: 1.7.1
3+
Date: 2026-01-10
4+
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")
77
)

NEWS.md

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

3-
# echarty 1.7.1 latest in development
3+
# echarty 1.7.2 in development
44

5-
* fixes to 1.7.0, updated coder.R
5+
* added _matrixIndex_ in .renumber
66

7-
# echarty 1.7.0 on CRAN
7+
# echarty 1.7.1 on CRAN
88

9-
* _ec.init_ support for _on_,_off_,_register*_,etc. chart instance and echarts API
9+
* fixes: updated demos code; check for empty df; custom charts .js file names
10+
* enhance flame chart with vertical labels (ec$vlevel)
11+
12+
# echarty 1.7.0
13+
14+
* new _ec.init_ parameter 'iniOpts' to support _on_,_off_,_register*_,etc. for chart instance
1015
* new value 'loadRemote' for parameter _ask_ to serve new v.6 custom-series
1116
* ability to attach data columns to item styles through _encode$data_
1217
* new flame (or icicle) hierarchical chart support as custom series

R/echarty.R

Lines changed: 43 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ NULL
1010
lenv <- new.env(parent = emptyenv())
1111
lenv$coNames <- ''
1212
# assign('aa', 11, envir=.ecv.colnames); get('aa', envir=.ecv.colnames)
13-
noAxisXY <- c('radar','parallel','themeRiver','map','gauge','pie','funnel','polar',
13+
noAxisXY <- c('radar','parallel','themeRiver','map','gauge','pie','funnel','polar','chord',
1414
'sunburst','tree','treemap','sankey','lines', 'liquidFill','wordCloud') # series
1515
noCoord <- c('polar','radar','singleAxis','parallelAxis','calendar')
1616
# using list(show=TRUE) or list(list()) is to create empty object{} in JS
@@ -24,11 +24,10 @@ noCoord <- c('polar','radar','singleAxis','parallelAxis','calendar')
2424
#' Best practice is to have the grouping column placed last. Grouping column cannot be used as axis.\cr
2525
#' Timeline requires a _grouped data.frame_ to build its \href{https://echarts.apache.org/en/option.html#options}{options}.\cr
2626
#' If grouping is on multiple columns, only the first one is used to determine settings.
27-
# ' REM @param ctype Chart type, default is 'scatter'. Could be set in _series.param_ instead.
2827
#' @param preset Boolean (default TRUE). Build preset attributes like dataset, series, xAxis, yAxis, etc.\cr
2928
#' When preset is FALSE, these attributes need to be set explicitly.\cr
3029
#' @param series.param Additional attributes for single preset series, default is NULL.\cr
31-
#' Defines a **single** series for both non-timeline and timeline charts. \cr
30+
#' Defines a **single** series for both non-timeline and timeline charts. Default type is 'scatter'. \cr
3231
#' **Multiple** series need to be defined directly with _series=list(list(type=...),list(type=...))_ or added with [ec.upd].
3332
#' @param tl.series Deprecated, use _timeline_ and _series.param_ instead.\cr
3433
#' @param ... Optional widget attributes. See Details. \cr
@@ -153,7 +152,7 @@ ec.init <- function( df= NULL, preset= TRUE, ..., #ctype= 'scatter',
153152
opt1 <- list(...)
154153
lenv$coNames <- ''
155154
# treacherous R does "partial matching of argument names" (like a bug):
156-
# if 'series.param' is before ... and 'series' is added, the latter is ignored!
155+
# if 'series.param' is before '...' and 'series' is added, the latter is ignored!
157156
elementId <- opt1$elementId; js <- opt1$js
158157
ask <- if (is.null(opt1$ask)) FALSE else opt1$ask
159158
ctype <- if (is.null(opt1$ctype)) 'scatter' else opt1$ctype
@@ -277,7 +276,6 @@ ec.init <- function( df= NULL, preset= TRUE, ..., #ctype= 'scatter',
277276
if (!is.null(df)) {
278277
stopifnot('ec.init: df should be data.frame or SharedData'=
279278
inherits(df, c("SharedData", "data.frame")))
280-
lenv$coNames <- colnames(df) # must execute before first ec.clmn
281279

282280
ct.key <- ct.group <- ct.dfKey <- NULL
283281
if (requireNamespace("crosstalk", quietly= TRUE)) {
@@ -295,6 +293,11 @@ ec.init <- function( df= NULL, preset= TRUE, ..., #ctype= 'scatter',
295293
crosstalk_key = ct.key,
296294
crosstalk_group = ct.group
297295
)
296+
stopifnot('ec.init: df is empty'= length(colnames(df)) > 0)
297+
# silently auto-add extra column for some charts and for colX/colY
298+
if (length(colnames(df)) == 1 && !is.grouped_df(df))
299+
df <- df |> mutate(duplicate= 1:nrow(df))
300+
lenv$coNames <- colnames(df) # must execute before first ec.clmn
298301
# if data.frame given, build dataset regardless of 'preset' or 'dataset'
299302

300303
# column-to-style with encode, replaces dataset column names, then creates series.data
@@ -315,8 +318,8 @@ ec.init <- function( df= NULL, preset= TRUE, ..., #ctype= 'scatter',
315318
df$value <- list(list(rep('', length(vv))))
316319
for(i in 1:nrow(df)) {
317320
tmp <- list()
318-
for(k in 1:length(vv)) tmp <- c(tmp, unlist(df[i,vv[k]], use.names=F))
319-
df[i,]$value <- list(tmp) #list(list(unlist(df[i,vv[1]], use.names=F), unlist(df[i,vv[2]], use.names=F)))
321+
for(k in seq_along(vv)) tmp <- c(tmp, unlist(df[i,vv[k]], use.names=F))
322+
df[i,]$value <- list(tmp)
320323
}
321324
# TODO: remove vv columns from df or not?
322325
sedval <- series.param$encode$data$value # save for axes
@@ -487,7 +490,7 @@ ec.init <- function( df= NULL, preset= TRUE, ..., #ctype= 'scatter',
487490
if (!is.null(series.param)) {
488491
x$opts$series <- .merlis(x$opts$series, series.param)
489492
}
490-
axad <- 1;
493+
axad <- 1; isSname <- FALSE
491494
x$opts$series <- lapply(x$opts$series, function(ss) {
492495
tmp <- xyNamesCS(ss)
493496
if (!is.null(tmp$c)) ss$coordinateSystem <- tmp$c
@@ -505,6 +508,7 @@ ec.init <- function( df= NULL, preset= TRUE, ..., #ctype= 'scatter',
505508
}
506509
# else don't overwrite user's encode
507510
}
511+
if (!is.null(ss$name)) isSname <<- TRUE
508512

509513
# renderItem helper only for our custom functions ri*
510514
# new ECharts custom series, like segmentedDoughnut, want a character string
@@ -518,6 +522,7 @@ ec.init <- function( df= NULL, preset= TRUE, ..., #ctype= 'scatter',
518522
if (!'yAxis' %in% namop) x$opts$yAxis <- list(show=TRUE)
519523
}
520524
if (dbg) cat('\n axad=',axad)
525+
if ((!'legend' %in% namop) && isSname) cat("\nNote: Some series have names, could add legend with 'legend= list(show=T)'")
521526

522527
# reading from encode set above # TODO: when names not 'x','y' ?
523528
tmp <- series.param$encode
@@ -620,7 +625,7 @@ ec.init <- function( df= NULL, preset= TRUE, ..., #ctype= 'scatter',
620625

621626
if (dbg) cat('\n colp=',unlist(colp),' ctyp=',unlist(ctyp))
622627
} # end isAxes
623-
628+
624629
} # end preset
625630

626631
x$opts <- .renumber(x$opts)
@@ -810,9 +815,10 @@ ec.init <- function( df= NULL, preset= TRUE, ..., #ctype= 'scatter',
810815
nops <- names(opt1) # add defaults 3D
811816
for(x in c('xAxis3D','yAxis3D','zAxis3D','grid3D')) {
812817
a2d <- sub('3D','',x)
813-
if (!(x %in% nops))
818+
if (!(x %in% nops)) {
814819
wt$x$opts[[x]] <- if (!is.null(wt$x$opts[[a2d]])) wt$x$opts[[a2d]]
815820
else list(show=TRUE)
821+
}
816822
}
817823
}
818824
wt$x$opts$xAxis <- wt$x$opts$yAxis <- NULL
@@ -870,7 +876,7 @@ ec.init <- function( df= NULL, preset= TRUE, ..., #ctype= 'scatter',
870876
tmp <- xyNamesCS(tl.series)
871877
xtem <- tmp$x; ytem <- tmp$y
872878
if (!is.null(tmp$c)) tl.series$coordinateSystem <- tmp$c
873-
if (dbg) cat('\ntimeline: x=',xtem,' y=',ytem,' cs=',tmp$c)
879+
if (dbg) cat('\ntimeline: x=',xtem,' y=',ytem,' cs=',tmp$c, ' encode=',unlist(tl.series$encode))
874880

875881
if (any(c('geo','leaflet') %in% tl.series$coordinateSystem)) {
876882
klo <- 'lng'; kla <- 'lat'
@@ -940,7 +946,7 @@ ec.init <- function( df= NULL, preset= TRUE, ..., #ctype= 'scatter',
940946
# define additional filter transforms and option series based on groupBy
941947
dsf <- optm <- list() # new filters and options
942948
filterIdx <- 0
943-
for (ii in 1:length(unlist(unique(df[[grnm]]))) ) { #unlist(unique(
949+
for (ii in seq_along(unlist(unique(df[[grnm]]))) ) { #unlist(unique(
944950
snames <- c()
945951
for (x2 in unlist(unique(df[tgrpBy]), use.names=FALSE) ) {
946952
dst <- opt1$dataset[[ii+1]] # skip source-dataset 1st
@@ -997,7 +1003,7 @@ ec.init <- function( df= NULL, preset= TRUE, ..., #ctype= 'scatter',
9971003
#' mutate(lwr= y-runif(30, 1, 3), upr= y+runif(30, 2, 4))
9981004
#' band.df <- df |> group_by(cat) |> group_split()
9991005
#' sband <- list()
1000-
#' for(ii in 1:length(band.df)) # build all bands
1006+
#' for(ii in seq_along(band.df)) # build all bands
10011007
#' sband <- append(sband,
10021008
#' ecr.band(band.df[[ii]], 'lwr', 'upr', type='stack', smooth=FALSE,
10031009
#' name= unique(band.df[[ii]]$cat), areaStyle= list(color=c('blue','green','yellow')[ii]))
@@ -1434,7 +1440,8 @@ ec.plugjs <- function(wt=NULL, source=NULL, ask=FALSE) {
14341440

14351441
if (ask=='loadRemote' && startsWith(source, 'http')) {
14361442
name <- regmatches(source, regexpr("custom-series/(.*?)/dist", source, perl=TRUE))
1437-
name <- gsub("custom-series/|/dist", "", name)
1443+
if (length(name)>0) name <- gsub("custom-series/|/dist", "", name)
1444+
else name <- fname
14381445
dep <- htmltools::htmlDependency(
14391446
name= name, version= '1.1.1',
14401447
src= list(href= dirname(source)), script= fname
@@ -1479,54 +1486,47 @@ ec.plugjs <- function(wt=NULL, source=NULL, ask=FALSE) {
14791486

14801487
# convert from R to JS numbering
14811488
.renumber <- function(opa) {
1482-
doEncode <- function(el) {
1483-
for(i in 1:length(el$encode)) {
1489+
decro <- function(x, params) { # decrement all elements of x
1490+
for(el in params) {
1491+
if (!is.null(x[[el]])) {
1492+
stopifnot('Some index is not numeric'= is.numeric(x[[el]]))
1493+
x[[el]] <- x[[el]] -1
1494+
}
1495+
}
1496+
x
1497+
}
1498+
doEncode <- function(el) { # for tooltip, seriesName, itemId, itemName, itemGroupId, itemChildGroupId
1499+
for(i in seq_along(el$encode)) {
14841500
if (!is.numeric(el$encode[[i]])) next
14851501
el$encode[[i]] <- el$encode[[i]] -1
14861502
}
14871503
el
14881504
}
1489-
r2jsEncode <- function(ss) {
1490-
1505+
r2js <- function(ss) {
14911506
if (any(names(ss)=='encode') && length(ss$encode)>0) ss <- doEncode(ss)
1492-
1493-
#if (!is.null(ss$type) && !ss$type %in% noAxisXY) {
1494-
if (!is.null(ss$xAxisIndex)) ss$xAxisIndex <- ss$xAxisIndex -1
1495-
if (!is.null(ss$yAxisIndex)) ss$yAxisIndex <- ss$yAxisIndex -1
1496-
#}
1497-
if (!is.null(ss$datasetIndex)) ss$datasetIndex <- ss$datasetIndex -1
1498-
if (!is.null(ss$geoIndex)) ss$geoIndex <- ss$geoIndex -1
1499-
if (!is.null(ss$polarIndex)) ss$polarIndex <- ss$polarIndex -1
1500-
if (!is.null(ss$calendarIndex)) ss$calendarIndex <- ss$calendarIndex -1
1501-
if (!is.null(ss$radarIndex)) ss$radarIndex <- ss$radarIndex -1
1502-
ss
1507+
decro(ss, c('xAxisIndex','yAxisIndex','singleAxisIndex','datasetIndex','geoIndex','polarIndex','calendarIndex','radarIndex'))
15031508
}
15041509

1505-
if (!is.null(opa$series)) opa$series <- lapply(opa$series, r2jsEncode)
1510+
if (!is.null(opa$series)) opa$series <- lapply(opa$series, r2js)
15061511
if (!is.null(opa$dataZoom)) {
1507-
if (all(sapply(opa$dataZoom, is.list))) opa$dataZoom <- lapply(opa$dataZoom, r2jsEncode)
1508-
else opa$dataZoom <- r2jsEncode(opa$dataZoom)
1512+
if (all(sapply(opa$dataZoom, is.list))) opa$dataZoom <- lapply(opa$dataZoom, r2js)
1513+
else opa$dataZoom <- r2js(opa$dataZoom)
15091514
}
15101515

1511-
decro <- function(x) {
1512-
if (!is.null(x$dimension) && is.numeric(x$dimension)) x$dimension <- x$dimension -1
1513-
if (!is.null(x$seriesIndex)) x$seriesIndex <- x$seriesIndex -1 # vMap
1514-
if (!is.null(x$gridIndex)) x$gridIndex <- x$gridIndex -1 # x/y Axis
1515-
x
1516-
}
15171516
decType <- \(typ) { # handle single or multiple items
1518-
item <- opa[[typ]]
1517+
item <- opa[[typ]]; pars <- c('gridIndex','calendarIndex', 'dimension','seriesIndex','categories')
15191518
if (!is.null(item)) {
15201519
if (all(sapply(item, is.list)))
1521-
opa[[typ]] <<- lapply(item, decro)
1522-
else
1523-
opa[[typ]] <<- decro(item)
1520+
opa[[typ]] <<- lapply(item, decro, pars) #...
1521+
else # last 3 are vMap
1522+
opa[[typ]] <<- decro(item, pars)
15241523
}
15251524
}
15261525
decType('xAxis')
15271526
decType('yAxis')
15281527
decType('visualMap')
1529-
opa
1528+
# some top elements may have matrixIndex
1529+
opa <- lapply(opa, \(xx) { if (any(names(xx)=='matrixIndex')) xx$matrixIndex <- xx$matrixIndex -1; xx })
15301530
}
15311531

15321532
# merge named lists: list OR list.of.lists like series

R/util.R

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -519,11 +519,11 @@ ec.util <- function(cmd='sf.series', ..., js=NULL, event='click') {
519519
#' \item **values** = list for customized \href{https://echarts.apache.org/en/option.html#series-scatter.data}{series.data} \cr
520520
#' \item **names** = named lists useful for named data like \href{https://echarts.apache.org/en/option.html#series-sankey.links}{sankey links}.
521521
#' \item **dendrogram** = build series data for Hierarchical Clustering dendrogram
522+
#' \item **flame** = build series data (lists of name,id,value) for hierarchy display by _renderItem_
522523
#' \item **treePC** = build series data for tree charts from parent/children data.frame
523-
#' \item **treeTT** = build series data for tree charts from data.frame like Titanic.\cr
524+
#' \item **treeTT** = build series data for tree charts from data.frame like Titanic.
524525
#' \item **boxplot** = build dataset and source lists, see Details
525526
#' \item **borders** = build geoJson string from map_data region borders, see Details
526-
#' \item **flame** = list of hierarchical series data to be presented by _renderItem_\cr
527527
#' }
528528
#' @param header for dataset, to include the column names or not, default TRUE. Set it to FALSE for \href{https://echarts.apache.org/en/option.html#series-scatter.data}{series.data}.\cr
529529
#' @param ... optional parameters\cr
@@ -607,6 +607,7 @@ ec.util <- function(cmd='sf.series', ..., js=NULL, event='click') {
607607
#' @export
608608
ec.data <- function(df, format='dataset', header=FALSE, ...) {
609609
stopifnot('ec.data: expecting parameter df'= !missing(df))
610+
args <- list(...)
610611

611612
if (format=='dendrogram') {
612613
stopifnot('ec.data: df should be hclust for dendrogram'= inherits(df, 'hclust'))
@@ -646,7 +647,7 @@ ec.data <- function(df, format='dataset', header=FALSE, ...) {
646647
}
647648
if (format=='flame') {
648649
stopifnot('ec.data: df should be a list for flame chart'= inherits(df, 'list'))
649-
filter_json <- function(json, id) {
650+
filter_json <- function(json, id=NULL) {
650651
if (is.null(id)) return(json)
651652

652653
recur <- function(item, id) {
@@ -667,21 +668,22 @@ ec.data <- function(df, format='dataset', header=FALSE, ...) {
667668
return(recur(json, id))
668669
}
669670
root_val <- 1000
670-
recursion_json <- function(json_obj, id) {
671+
recursion_json <- function(json_obj, id=NULL) {
671672
data <- list()
672673
filtered_json <- filter_json(json_obj, id) # Note: R doesn't have structuredClone by default
673674
recur <- function(item, start = 0, level = 0, wit = NULL) {
674-
temp <- list(
675-
name = item$name,
676-
id = item$name,
677-
value = c(
675+
val <- c(
678676
level,
679677
start,
680678
start + wit,
681679
item$name,
682-
round(wit / root_val * 100, 2),
683-
item$value
684-
)
680+
round(wit / root_val * 100, 2)
681+
)
682+
if (!is.null(item$value)) val <- c(val, item$value)
683+
temp <- list(
684+
name = item$name,
685+
id = item$name,
686+
value = val
685687
)
686688
data <<- c(data, list(temp)) # Use <<- to modify parent scope
687689

@@ -701,7 +703,6 @@ ec.data <- function(df, format='dataset', header=FALSE, ...) {
701703
return(data)
702704
}
703705

704-
args <- list(...)
705706
out <- recursion_json(df, args$name)
706707
return(out)
707708
}
@@ -788,7 +789,6 @@ ec.data <- function(df, format='dataset', header=FALSE, ...) {
788789
tmp <- lapply(n, \(i) lapply(df, "[[", i)) # rows to lists, preserve column types
789790

790791
if (format=='boxplot') {
791-
args <- list(...)
792792
rady <- if ('ol.radius' %in% names(args)) args$ol.radius else NULL
793793
jitter <- if ('jitter' %in% names(args)) args$jitter else 0
794794
layout <- if ('layout' %in% names(args)) args$layout else 'h'
@@ -940,7 +940,6 @@ ec.data <- function(df, format='dataset', header=FALSE, ...) {
940940
datset <- lapply(tmp, \(x) list(value=unlist(x, use.names=FALSE)))
941941
}
942942
else { # format=='names'
943-
args <- list(...)
944943
if ('nasep' %in% names(args)) {
945944
stopifnot("data('names'): nasep should be 1 char"= nchar(args$nasep)==1)
946945
# names separator is present, replace compound names with nested lists

demo/coder.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
devAskNewPage(ask = FALSE)
66
if (interactive()) {
77
# this stopped working - "cannot open URL"
8-
#library(shiny)
8+
library(shiny)
99
#shiny::runGist('https://gist.github.com/helgasoft/02b257429e78e138f87aefce14f7aebc')
1010

1111
# this loads, but gist cannot be edited

0 commit comments

Comments
 (0)