[med-svn] [r-cran-scatterd3] 01/08: New upstream version 0.7+dfsg
Andreas Tille
tille at debian.org
Tue Nov 8 10:22:51 UTC 2016
This is an automated email from the git hooks/post-receive script.
tille pushed a commit to branch master
in repository r-cran-scatterd3.
commit 41e198ffee16bb930ea31d62d95bdadd94bc181a
Author: Andreas Tille <tille at debian.org>
Date: Tue Nov 8 10:05:08 2016 +0100
New upstream version 0.7+dfsg
---
DESCRIPTION | 8 +-
MD5 | 59 +-
NEWS | 23 +
R/scatterD3.R | 458 ++++---
README.md | 72 +-
build/vignette.rds | Bin 201 -> 223 bytes
inst/doc/introduction.R | 128 +-
inst/doc/introduction.Rmd | 253 +++-
inst/doc/introduction.html | 363 ++++--
inst/htmlwidgets/lib/d3-lasso-plugin/lasso.js | 94 +-
inst/htmlwidgets/lib/scatterD3.css | 29 -
inst/htmlwidgets/scatterD3-arrows.js | 62 +
inst/htmlwidgets/scatterD3-axes.js | 43 +
inst/htmlwidgets/scatterD3-dots.js | 85 ++
inst/htmlwidgets/scatterD3-ellipses.js | 36 +
inst/htmlwidgets/scatterD3-exports.js | 37 +
inst/htmlwidgets/scatterD3-labels.js | 36 +
inst/htmlwidgets/scatterD3-lasso.js | 159 +++
inst/htmlwidgets/scatterD3-legend.js | 227 ++++
inst/htmlwidgets/scatterD3-lines.js | 50 +
inst/htmlwidgets/scatterD3-setup.js | 192 +++
inst/htmlwidgets/scatterD3-utils.js | 33 +
inst/htmlwidgets/scatterD3.css | 99 ++
inst/htmlwidgets/scatterD3.js | 1636 +++++++++----------------
inst/htmlwidgets/scatterD3.yaml | 44 +-
man/scatterD3.Rd | 131 +-
vignettes/introduction.Rmd | 253 +++-
27 files changed, 3084 insertions(+), 1526 deletions(-)
diff --git a/DESCRIPTION b/DESCRIPTION
index 8cef2ed..9ce6b26 100644
--- a/DESCRIPTION
+++ b/DESCRIPTION
@@ -1,8 +1,8 @@
Package: scatterD3
Type: Package
Title: D3 JavaScript Scatterplot from R
-Version: 0.6.2
-Date: 2016-03-24
+Version: 0.7
+Date: 2016-10-26
Authors at R: c(
person(
"Julien", "Barnier",
@@ -43,7 +43,7 @@ Imports: htmlwidgets, digest, ellipse
Suggests: knitr, rmarkdown
RoxygenNote: 5.0.1
NeedsCompilation: no
-Packaged: 2016-03-24 13:27:43 UTC; julien
+Packaged: 2016-10-26 14:09:26 UTC; julien
Author: Julien Barnier [aut, cre],
Kent Russell [aut, ctb],
Mike Bostock [aut, cph] (d3.js library, http://d3js.org),
@@ -51,4 +51,4 @@ Author: Julien Barnier [aut, cre],
Speros Kokenes [aut, cph] (d3-lasso-plugin library,
https://github.com/skokenes/D3-Lasso-Plugin)
Repository: CRAN
-Date/Publication: 2016-03-24 23:53:34
+Date/Publication: 2016-10-26 18:13:27
diff --git a/MD5 b/MD5
index 2f0d2ef..58fa0f4 100644
--- a/MD5
+++ b/MD5
@@ -1,23 +1,50 @@
-4eb9c371af7bdea6941fe915b5619e7f *DESCRIPTION
+2bcb5b6b0eb20b5bc2f61f1ac4dfa3f6 *DESCRIPTION
6175be66a588675cfcc631efac40bccb *NAMESPACE
-fb53364a6e90e8b83fe6035ef6b6e44d *NEWS
+3f2bbe9c8bae51d0166d1b11a3a82fc7 *NEWS
cceffe7465fc66364cdfb8c78319bcb7 *R/scatterD3-Rd.R
-c0bf7b8873d49b0336368e68ca5f4097 *R/scatterD3.R
-29908a3564f2d24132dd533219e2e9bf *README.md
-bffb60ce3b41d716319d5e3909b064ab *build/vignette.rds
-3d34bf1d499c53db86a66766defdf517 *inst/doc/introduction.R
-c286a29f2adff2645c64d844b6cba45b *inst/doc/introduction.Rmd
-9171e7d9d3f5c1175c231c3a42aede80 *inst/doc/introduction.html
+3c316b369e378f9678d6a2e0ac3909d0 *R/scatterD3.R
+166ebf80e55dc8700409ac1b784a6672 *README.md
+269ab1b5eb2eba8f29cacdee5504e66c *build/vignette.rds
+93120ed46baccae9234c9b3306ca2f68 *inst/doc/introduction.R
+dcc7b4cc9a357e0d3c9de4f4bc6d08b0 *inst/doc/introduction.Rmd
+d0ef5daebc371a6978bfdb137a1601eb *inst/doc/introduction.html
9a43e3ae9eeb6821b99b41158d0b08cd *inst/htmlwidgets/lib/LICENSE
-e9b501452b74836764e9c186f2ed337e *inst/htmlwidgets/lib/d3-3.5.6.min.js
59827795b5ab14a5edc7e6bf8744cef7 *inst/htmlwidgets/lib/d3-lasso-plugin/LICENSE
95f5a84cb5c61c5abb84da94f8c87b2c *inst/htmlwidgets/lib/d3-lasso-plugin/README.md
ef54456443f8077b51e73565ef195f96 *inst/htmlwidgets/lib/d3-lasso-plugin/lasso.css
-53a84687b07856608ed1d57fc5adb6e1 *inst/htmlwidgets/lib/d3-lasso-plugin/lasso.js
-1fee5abd329e5719a45e7aca380e80c6 *inst/htmlwidgets/lib/d3-legend.min.js
-210c678aef0965f051785069e82700ca *inst/htmlwidgets/lib/scatterD3.css
-d6704d4f9ce37bf03d163ad3b052e2f3 *inst/htmlwidgets/scatterD3.js
-221e87f55ec78ce2a5e7b048de150287 *inst/htmlwidgets/scatterD3.yaml
+b5abd75e65309ad41d8d2648d57889c0 *inst/htmlwidgets/lib/d3-lasso-plugin/lasso.js
+a8d4667973aca44596df48c617bca8a7 *inst/htmlwidgets/lib/d3/d3-4.2.6.min.js
+45e82b5ddf03af6e8393b0ea25ce2a16 *inst/htmlwidgets/lib/d3/d3-array.v1.min.js
+74c3a3a01c70eee2a2532289a06e017a *inst/htmlwidgets/lib/d3/d3-axis.v1.min.js
+3e256c8345407e84bfc989b2ce024dfd *inst/htmlwidgets/lib/d3/d3-collection.v1.min.js
+02590853c7cf5b7a23e10d4661b74e25 *inst/htmlwidgets/lib/d3/d3-color.v1.min.js
+29780371efc88d480b7a147b4cc8507b *inst/htmlwidgets/lib/d3/d3-dispatch.v1.min.js
+2ca52bf1e01c9b3bbae12be0e7d2077d *inst/htmlwidgets/lib/d3/d3-drag.v1.min.js
+3224032bbbba6161e6cf5a9633855964 *inst/htmlwidgets/lib/d3/d3-ease.v1.min.js
+84ff2863fe8a20653d62c586f1ea5e35 *inst/htmlwidgets/lib/d3/d3-format.v1.min.js
+0ca13371c0a586582aac5b034324f6da *inst/htmlwidgets/lib/d3/d3-interpolate.v1.min.js
+cfb4cd20f7f643fa9223be4cddfcd567 *inst/htmlwidgets/lib/d3/d3-legend.min.js
+947e02f210bbf5e5f5574225d8ad3c7f *inst/htmlwidgets/lib/d3/d3-path.v1.min.js
+3e0aed6bf28f858c23cde072772bdda9 *inst/htmlwidgets/lib/d3/d3-scale.v1.min.js
+5e9db58238cbc21463e32b349efb3746 *inst/htmlwidgets/lib/d3/d3-selection.v1.min.js
+d7d1e21695f513b6f6611c7f071c6a80 *inst/htmlwidgets/lib/d3/d3-shape.v1.min.js
+ebd16393d06ae1d06545d20ad28a3536 *inst/htmlwidgets/lib/d3/d3-timer.v1.min.js
+9cc1892d4422f3d37583715c9b9b1277 *inst/htmlwidgets/lib/d3/d3-transition.v1.min.js
+53a7a6c7509864215bc4b861ab0af01c *inst/htmlwidgets/lib/d3/d3-zoom-patched.min.js
+9f5448c9c91f9f199a3e431dc36e3962 *inst/htmlwidgets/scatterD3-arrows.js
+98049707cdc14f18f4111d489618bed6 *inst/htmlwidgets/scatterD3-axes.js
+6109b680c252f86e7388d74cd793e5f2 *inst/htmlwidgets/scatterD3-dots.js
+fc0b93862a3ce9472d9b4473cb7775bf *inst/htmlwidgets/scatterD3-ellipses.js
+712d86fcf3622b6cda7b7736ba87036a *inst/htmlwidgets/scatterD3-exports.js
+cef211b739ec3f110ce6f91951b0c77f *inst/htmlwidgets/scatterD3-labels.js
+824f0e4e0ef43e1c66a7130f4a2ee2e5 *inst/htmlwidgets/scatterD3-lasso.js
+fcb449b2588e423278ff9a5c6a4f4e30 *inst/htmlwidgets/scatterD3-legend.js
+492209fcdc68ca591a15f072432c3071 *inst/htmlwidgets/scatterD3-lines.js
+d872e0e9b6d2da587d5d215c896f7847 *inst/htmlwidgets/scatterD3-setup.js
+479d82da539716c35e8a17a320136601 *inst/htmlwidgets/scatterD3-utils.js
+43634992a6de72436671f4c03658f777 *inst/htmlwidgets/scatterD3.css
+e8ed0f24fa1e1a02724fb40290c76405 *inst/htmlwidgets/scatterD3.js
+dee4ef6434928b083890f389fed71af4 *inst/htmlwidgets/scatterD3.yaml
12229f19d7fb5d246373d4a3e633534b *man/scatterD3-shiny.Rd
-1bb1c38f66153ee3f513b5ac933ec8e9 *man/scatterD3.Rd
-c286a29f2adff2645c64d844b6cba45b *vignettes/introduction.Rmd
+1bda725eb66976c5fa6b3e14756ec544 *man/scatterD3.Rd
+dcc7b4cc9a357e0d3c9de4f4bc6d08b0 *vignettes/introduction.Rmd
diff --git a/NEWS b/NEWS
index d3ede41..c2f8d57 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,26 @@
+scatterD3 0.7
+------------------------------------------------------
+
+* Upgrade to d3v4
+* Add `data` argument to specify variables from a data frame with NSE
+* New menu accessible directly from the graph to allow zoom resetting, SVG export, etc.
+* Continuous color scales are now supported. They are automatically guessed from `col_var` characteristics, but can be forced with the `col_continuous` argument.
+* Categorical variables are now allowed for `x` and `y`.
+* New `lines` argument to add custom lines to the plot
+* New argument : `opacity_var` to specify points opacity individually with a vector. Use `point_opacity` to specify a constant opacity.
+* New argument : `url_var` to specify URLs to be opened when a point is clicked.
+* Add `click_callback` parameter, opening a hook for a click event listener (thanks @detule and @harveyl888)
+* Add `zoom_callback` parameter, opening a hook for a zoom event listener
+* New "export labels position" feature
+* New settings `hover_size` and `hover_opacity` (thanks @nicolabo)
+* Axes and legend font size customization with `axes_font_size` and `legend_font_size` (thanks @fineswag)
+* Better legend transitions when updating
+* JavaScript code split into several subfiles
+* Left margin customization with the `left_margin` argument
+* More precise font-family specification for (hopefully) better rendering
+* Bugfix : blank plot when only one color passed to `colors` (thanks @chewth)
+
+
scatterD3 0.6.2
------------------------------------------------------
diff --git a/R/scatterD3.R b/R/scatterD3.R
index e37bfec..454bd95 100644
--- a/R/scatterD3.R
+++ b/R/scatterD3.R
@@ -2,43 +2,86 @@
#'
#' Interactive scatter plots based on htmlwidgets and d3.js
#'
-#' @param x numerical vector of x values
-#' @param y numerical vector of y values
-#' @param lab optional character vector of text labels
+#' @param data default dataset to use for plot.
+#' @param x numerical vector of x values, or variable name if data is not NULL
+#' @param y numerical vector of y values, or variable name if data is not NULL
+#' @param lab optional character vector of text labels, or variable name if
+#' data is not NULL
#' @param point_size points size. Ignored if size_var is not NULL.
#' @param labels_size text labels size
-#' @param point_opacity points opacity, as an integer (same opacity for all points) or a vector of integers
+#' @param point_opacity points opacity, as an integer (same opacity for all
+#' points) or a vector of integers, or variable name if data is not NULL
#' @param fixed force a 1:1 aspect ratio
-#' @param col_var optional vector for points color mapping
-#' @param colors vector of custom points colors. Colors must be
-#' defined as an hexadecimal string (eg "#FF0000"). If
-#' \code{colors} is a named list or vector, then the colors will
-#' be associated with their name within \code{col_var}.
-#' @param ellipses draw confidence ellipses for points or the different color mapping groups
+#' @param col_var optional vector for points color mapping, or variable name
+#' if data is not NULL
+#' @param col_continuous specify if the color scale must be continuous. By
+#' default, if \code{col_var} is numeric, not a factor, and has more than
+#' 6 unique values, it is considered as continuous.
+#' @param colors vector of custom points colors. Colors must be defined as an
+#' hexadecimal string (eg "#FF0000"). If \code{colors} is a named list or
+#' a named vector, then the colors will be associated with their name
+#' within \code{col_var}. Ignored for a continuous color scale.
+#' @param ellipses draw confidence ellipses for points or the different color
+#' mapping groups
#' @param ellipses_level confidence level for ellipses (0.95 by default)
-#' @param symbol_var optional vector for points symbol mapping
-#' @param size_var optional vector for points size mapping
-#' @param size_range numeric vector of length 2, giving the minimum and maximum point sizes when mapping with size_var
+#' @param symbol_var optional vector for points symbol mapping, or variable
+#' name if data is not NULL
+#' @param size_var optional vector for points size mapping, or variable name
+#' if data is not NULL
+#' @param size_range numeric vector of length 2, giving the minimum and
+#' maximum point sizes when mapping with size_var
#' @param col_lab color legend title
#' @param symbol_lab symbols legend title
#' @param size_lab size legend title
-#' @param key_var optional vector of rows ids. This is passed as a key to d3, and is only added in shiny apps where displayed rows are filtered interactively.
-#' @param type_var optional vector of points type : "point" for adot (default), "arrow" for an arrow starting from the origin.
+#' @param key_var optional vector of rows ids, or variable name if data is not
+#' NULL. This is passed as a key to d3, and is only added in shiny apps
+#' where displayed rows are filtered interactively.
+#' @param type_var optional vector of points type : "point" for adot
+#' (default), "arrow" for an arrow starting from the origin.
+#' @param opacity_var optional vector of points opacity (values between 0 and
+#' 1)
+#' @param url_var optional vector of URLs to be opened when a point is clicked
#' @param unit_circle set tot TRUE to draw a unit circle
#' @param tooltips logical value to display tooltips when hovering points
#' @param tooltip_text optional character vector of tooltips text
#' @param xlab x axis label
-#' @param ylab y axis label
+#' @param ylab y axis label.
+#' @param axes_font_size font size for axes text (any CSS compatible value)
+#' @param legend_font_size font size for legend text (any CSS compatible
+#' value)
+#' @param hover_size factor for changing size when hovering points
+#' @param hover_opacity points opacity when hovering
#' @param xlim numeric vector of length 2, manual x axis limits
#' @param ylim numeric vector of length 2, manual y axis limits
-#' @param lasso logical value to add {https://github.com/skokenes/D3-Lasso-Plugin}{d3-lasso-plugin} feature
-#' @param lasso_callback the body of a JavaScript callback function with the argument \code{sel} to be applied to a lasso plugin selection
-#' @param html_id manually specify an HTML id for the svg root node. A random one is generated by default.
-#' @param dom_id_reset_zoom HTML DOM id of the element to bind the "reset zoom" control to.
-#' @param dom_id_svg_export HTML DOM id of the element to bind the "svg export" control to.
-#' @param dom_id_lasso_toggle HTML DOM id of the element to bind the "toggle lasso" control to.
-#' @param transitions if TRUE, data updates are displayed with smooth transitions, if FALSE the whole chart is redrawn. Only used within shiny apps.
-#' @param legend_width legend area width, in pixels. Set to 0 to disable legend completely.
+#' @param menu wether to display the tools menu (gear icon)
+#' @param lasso logical value to add
+#' {https://github.com/skokenes/D3-Lasso-Plugin}{d3-lasso-plugin} feature
+#' @param lasso_callback the body of a JavaScript callback function with the
+#' argument \code{sel} to be applied to a lasso plugin selection
+#' @param click_callback the body of a JavaScript callback function whose
+#' inputs are html_id, and the index of the clicked element.
+#' @param zoom_callback the body of a JavaScript callback function whose
+#' inputs are the new xmin, xmax, ymin and ymax after a zoom action is
+#' triggered.
+#' @param lines a data frame with at least the \code{slope} and
+#' \code{intercept} columns, and as many rows as lines to add to
+#' scatterplot. Style can be added with \code{stroke}, \code{stroke_width}
+#' and \code{stroke_dasharray} columns. To draw a vertical line, pass
+#' \code{Inf} as \code{slope} value.
+#' @param html_id manually specify an HTML id for the svg root node. A random
+#' one is generated by default.
+#' @param dom_id_reset_zoom HTML DOM id of the element to bind the
+#' "reset zoom" control to.
+#' @param dom_id_svg_export HTML DOM id of the element to bind the
+#' "svg export" control to.
+#' @param dom_id_lasso_toggle HTML DOM id of the element to bind the
+#' "toggle lasso" control to.
+#' @param transitions if TRUE, data updates are displayed with smooth
+#' transitions, if FALSE the whole chart is redrawn. Only used within
+#' shiny apps.
+#' @param legend_width legend area width, in pixels. Set to 0 to disable
+#' legend completely.
+#' @param left_margin margin on the left of the plot, in pixels
#' @param width figure width, computed when displayed
#' @param height figure height, computed when displayed
#'
@@ -53,7 +96,7 @@
#' D3.js was created by Michael Bostock. See \url{http://d3js.org/}
#'
#' @examples
-#' scatterD3(x = mtcars$wt, y = mtcars$mpg, lab = rownames(mtcars),
+#' scatterD3(x = mtcars$wt, y = mtcars$mpg, data=NULL, lab = rownames(mtcars),
#' col_var = mtcars$cyl, symbol_var = mtcars$am,
#' xlab = "Weight", ylab = "Mpg", col_lab = "Cylinders",
#' symbol_lab = "Manual transmission", html_id = NULL)
@@ -62,11 +105,15 @@
#' @importFrom stats cov
#' @importFrom htmlwidgets JS
#' @export
-#'
-scatterD3 <- function(x, y, lab = NULL,
+
+scatterD3 <- function(x, y, data = NULL, lab = NULL,
point_size = 64, labels_size = 10,
point_opacity = 1,
- fixed = FALSE, col_var = NULL,
+ hover_size = 1,
+ hover_opacity = NULL,
+ fixed = FALSE,
+ col_var = NULL,
+ col_continuous = NULL,
colors = NULL,
ellipses = FALSE,
ellipses_level = 0.95,
@@ -77,170 +124,249 @@ scatterD3 <- function(x, y, lab = NULL,
size_lab = NULL,
key_var = NULL,
type_var = NULL,
+ opacity_var = NULL,
unit_circle = FALSE,
+ url_var = NULL,
tooltips = TRUE,
tooltip_text = NULL,
xlab = NULL, ylab = NULL,
html_id = NULL,
width = NULL, height = NULL,
legend_width = 150,
+ left_margin = 30,
xlim = NULL, ylim = NULL,
dom_id_reset_zoom = "scatterD3-reset-zoom",
dom_id_svg_export = "scatterD3-svg-export",
dom_id_lasso_toggle = "scatterD3-lasso-toggle",
transitions = FALSE,
+ menu = TRUE,
lasso = FALSE,
- lasso_callback = NULL) {
-
- ## Variable names as default labels
- if (is.null(xlab)) xlab <- deparse(substitute(x))
- if (is.null(ylab)) ylab <- deparse(substitute(y))
- if (is.null(col_lab)) col_lab <- deparse(substitute(col_var))
- if (is.null(symbol_lab)) symbol_lab <- deparse(substitute(symbol_var))
- if (is.null(size_lab)) size_lab <- deparse(substitute(size_var))
- if (is.null(html_id)) html_id <- paste0("scatterD3-", paste0(sample(LETTERS,8,replace = TRUE),collapse = ""))
-
- # colors can be named
- # we'll need to convert named vector to a named list
- # for the JSON conversion
- if (!is.null(colors) && !is.null(names(colors))) {
- colors <- as.list(colors)
- if (!setequal(names(colors), unique(col_var))) warning("Set of colors and col_var values do not match")
- }
-
- ## data element
- data <- data.frame(x = x, y = y)
- if (!is.null(lab)) data <- cbind(data, lab = lab)
- if (!is.null(point_opacity)) data <- cbind(data, point_opacity = point_opacity)
- if (!is.null(col_var)) {
- col_var <- as.character(col_var)
- col_var[is.na(col_var)] <- "NA"
- data <- cbind(data, col_var = col_var)
- }
- if (!is.null(symbol_var)) {
- symbol_var <- as.character(symbol_var)
- symbol_var[is.na(symbol_var)] <- "NA"
- data <- cbind(data, symbol_var = symbol_var)
- }
- if (!is.null(size_var)) {
- warning("NA values in size_var. Values set to min(0, size_var)")
- size_var[is.na(size_var)] <- min(0, size_var, na.rm = TRUE)
- data <- cbind(data, size_var = size_var)
- }
- if (!is.null(type_var)) data <- cbind(data, type_var = type_var)
- if (!is.null(key_var)) data <- cbind(data, key_var = key_var)
- else data <- cbind(data, key_var = seq_along(x))
- if (!is.null(tooltip_text)) data <- cbind(data, tooltip_text = tooltip_text)
-
- ## Compute confidence ellipses point positions with ellipse::ellipse.default()
- compute_ellipse <- function(x, y, level = ellipses_level, npoints = 50) {
- cx <- mean(x)
- cy <- mean(y)
- data.frame(ellipse::ellipse(stats::cov(cbind(x,y)), centre = c(cx, cy), level = level, npoints = npoints))
- }
-
- ## Compute ellipses points data
- ellipses_data <- list()
- if (ellipses) {
- ## Only one ellipse
- if (is.null(col_var)) {
- ell <- compute_ellipse(x, y)
- ellipses_data <- append(ellipses_data, list(list(level = "_scatterD3_all", data = ell)))
- } else {
- ## One ellipse per col_var level
- for (l in unique(col_var)) {
- sel <- col_var == l & !is.na(col_var)
- if (sum(sel) > 2) {
- tmpx <- x[sel]
- tmpy <- y[sel]
- ell <- compute_ellipse(tmpx, tmpy)
- ellipses_data <- append(ellipses_data, list(list(level = l, data = ell)))
+ lasso_callback = NULL,
+ click_callback = NULL,
+ zoom_callback = NULL,
+ lines = data.frame(slope = c(0, Inf),
+ intercept = c(0, 0),
+ stroke_dasharray = c(5,5)),
+ axes_font_size = "100%",
+ legend_font_size = "100%") {
+
+ ## Variable names as default labels
+ if (is.null(xlab)) xlab <- deparse(substitute(x))
+ if (is.null(ylab)) ylab <- deparse(substitute(y))
+ if (is.null(col_lab)) col_lab <- deparse(substitute(col_var))
+ if (is.null(symbol_lab)) symbol_lab <- deparse(substitute(symbol_var))
+ if (is.null(size_lab)) size_lab <- deparse(substitute(size_var))
+ opacity_lab <- deparse(substitute(opacity_var))
+ if (is.null(html_id)) html_id <- paste0("scatterD3-", paste0(sample(LETTERS,8,replace = TRUE),collapse = ""))
+
+ ## NSE
+ if (!is.null(data)) {
+ null_or_name <- function(x) {
+ if (x != "NULL") return(data[, x])
+ else return(NULL)
+ }
+ ## Get variable names
+ x <- data[, deparse(substitute(x))]
+ y <- data[, deparse(substitute(y))]
+ lab <- deparse(substitute(lab))
+ col_var <- deparse(substitute(col_var))
+ size_var <- deparse(substitute(size_var))
+ symbol_var <- deparse(substitute(symbol_var))
+ opacity_var <- deparse(substitute(opacity_var))
+ url_var <- deparse(substitute(url_var))
+ key_var <- deparse(substitute(key_var))
+ ## Get variable data if not "NULL"
+ lab <- null_or_name(lab)
+ col_var <- null_or_name(col_var)
+ size_var <- null_or_name(size_var)
+ symbol_var <- null_or_name(symbol_var)
+ opacity_var <- null_or_name(opacity_var)
+ url_var <- null_or_name(url_var)
+ key_var <- null_or_name(key_var)
+ }
+
+ x_categorical <- is.factor(x) || !is.numeric(x)
+ y_categorical <- is.factor(y) || !is.numeric(y)
+
+ ## colors can be named
+ ## we'll need to convert named vector to a named list
+ ## for the JSON conversion
+ if (!is.null(colors) && !is.null(names(colors))) {
+ colors <- as.list(colors)
+ if (!setequal(names(colors), unique(col_var))) warning("Set of colors and col_var values do not match")
+ }
+
+ ## Determine from the data if we have a continuous or ordinal color scale
+ if (is.null(col_continuous)) {
+ col_continuous <- FALSE
+ if (!is.factor(col_var) && is.numeric(col_var) && length(unique(col_var)) > 6) {
+ col_continuous <- TRUE
+ }
+ }
+
+ ## data element
+ data <- data.frame(x = x, y = y)
+ if (!is.null(lab)) data <- cbind(data, lab = lab)
+ if (!is.null(col_var) && !col_continuous) {
+ col_var <- as.character(col_var)
+ col_var[is.na(col_var)] <- "NA"
+ data <- cbind(data, col_var = col_var)
+ }
+ if (!is.null(col_var) && col_continuous) {
+ if (any(is.na(col_var))) warning("NA values in continuous col_var. Values set to min(0, col_var)")
+ col_var[is.na(col_var)] <- min(0, col_var, na.rm = TRUE)
+ data <- cbind(data, col_var = col_var)
+ }
+ if (!is.null(symbol_var)) {
+ symbol_var <- as.character(symbol_var)
+ symbol_var[is.na(symbol_var)] <- "NA"
+ data <- cbind(data, symbol_var = symbol_var)
+ }
+ if (!is.null(size_var)) {
+ if (any(is.na(size_var))) warning("NA values in size_var. Values set to min(0, size_var)")
+ size_var[is.na(size_var)] <- min(0, size_var, na.rm = TRUE)
+ data <- cbind(data, size_var = size_var)
+ }
+ if (!is.null(type_var)) data <- cbind(data, type_var = type_var)
+ if (!is.null(url_var)) {
+ url_var[is.na(url_var)] <- ""
+ data <- cbind(data, url_var = url_var)
+ if (!is.null(click_callback)) {
+ click_callback <- NULL
+ warning("Both url_var and click_callback defined, click_callback set to NULL")
}
- }
}
- }
-
- ## List of hashes for each data variable, to track which data elements changed
- ## to apply updates and transitions in shiny app.
- hashes <- list()
- if (transitions) {
- for (var in c("x", "y", "lab", "key_var", "col_var", "symbol_var", "size_var", "ellipses_data", "point_opacity")) {
- hashes[[var]] <- digest::digest(get(var), algo = "sha256")
+ if (!is.null(opacity_var)) data <- cbind(data, opacity_var = opacity_var)
+ if (!is.null(key_var)) {
+ data <- cbind(data, key_var = key_var)
+ } else {
+ data <- cbind(data, key_var = seq_along(x))
+ }
+ if (!is.null(tooltip_text)) data <- cbind(data, tooltip_text = tooltip_text)
+
+ ## Compute confidence ellipses point positions with ellipse::ellipse.default()
+ compute_ellipse <- function(x, y, level = ellipses_level, npoints = 50) {
+ cx <- mean(x)
+ cy <- mean(y)
+ data.frame(ellipse::ellipse(stats::cov(cbind(x,y)), centre = c(cx, cy), level = level, npoints = npoints))
}
- }
-
- # create a list that contains the settings
- settings <- list(
- labels_size = labels_size,
- point_size = point_size,
- xlab = xlab,
- ylab = ylab,
- has_labels = !is.null(lab),
- col_var = col_var,
- col_lab = col_lab,
- colors = colors,
- ellipses = ellipses,
- ellipses_data = ellipses_data,
- symbol_var = symbol_var,
- symbol_lab = symbol_lab,
- size_var = size_var,
- size_range = size_range,
- size_lab = size_lab,
- key_var = key_var,
- type_var = type_var,
- unit_circle = unit_circle,
- has_color_var = !is.null(col_var),
- has_symbol_var = !is.null(symbol_var),
- has_size_var = !is.null(size_var),
- has_legend = !is.null(col_var) || !is.null(symbol_var) || !is.null(size_var),
- has_tooltips = tooltips,
- tooltip_text = tooltip_text,
- has_custom_tooltips = !is.null(tooltip_text),
- fixed = fixed,
- legend_width = legend_width,
- html_id = html_id,
- xlim = xlim,
- ylim = ylim,
- lasso = lasso,
- lasso_callback = htmlwidgets::JS(lasso_callback),
- dom_id_reset_zoom = dom_id_reset_zoom,
- dom_id_svg_export = dom_id_svg_export,
- dom_id_lasso_toggle = dom_id_lasso_toggle,
- transitions = transitions,
- hashes = hashes
- )
-
- # pass the data and settings using 'x'
- x <- list(
- data = data,
- settings = settings
- )
-
- # create widget
- htmlwidgets::createWidget(
- name = 'scatterD3',
- x,
- width = width,
- height = height,
- package = 'scatterD3',
- sizingPolicy = htmlwidgets::sizingPolicy(
- browser.fill = TRUE,
- viewer.fill = TRUE
- )
- )
+
+ ## Compute ellipses points data
+ ellipses_data <- list()
+ if (ellipses && !col_continuous && !x_categorical && !y_categorical) {
+ ## Only one ellipse
+ if (is.null(col_var)) {
+ ell <- compute_ellipse(x, y)
+ ellipses_data <- append(ellipses_data, list(list(level = "_scatterD3_all", data = ell)))
+ } else {
+ ## One ellipse per col_var level
+ for (l in unique(col_var)) {
+ sel <- col_var == l & !is.na(col_var)
+ if (sum(sel) > 2) {
+ tmpx <- x[sel]
+ tmpy <- y[sel]
+ ell <- compute_ellipse(tmpx, tmpy)
+ ellipses_data <- append(ellipses_data, list(list(level = l, data = ell)))
+ }
+ }
+ }
+ } else {
+ ## Force no ellipses if continuous color or categorical variable
+ ellipses <- FALSE
+ }
+
+ ## List of hashes for each data variable, to track which data elements changed
+ ## to apply updates and transitions in shiny app.
+ hashes <- list()
+ if (transitions) {
+ for (var in c("x", "y", "lab", "key_var", "col_var", "symbol_var", "size_var", "ellipses_data", "opacity_var", "lines")) {
+ hashes[[var]] <- digest::digest(get(var), algo = "sha256")
+ }
+ }
+
+ ## create a list that contains the settings
+ settings <- list(
+ labels_size = labels_size,
+ point_size = point_size,
+ point_opacity = point_opacity,
+ hover_size = hover_size,
+ hover_opacity = hover_opacity,
+ xlab = xlab,
+ ylab = ylab,
+ has_labels = !is.null(lab),
+ col_lab = col_lab,
+ col_continuous = col_continuous,
+ colors = colors,
+ ellipses = ellipses,
+ ellipses_data = ellipses_data,
+ symbol_lab = symbol_lab,
+ size_range = size_range,
+ size_lab = size_lab,
+ opacity_lab = opacity_lab,
+ unit_circle = unit_circle,
+ has_color_var = !is.null(col_var),
+ has_symbol_var = !is.null(symbol_var),
+ has_size_var = !is.null(size_var),
+ has_opacity_var = !is.null(opacity_var),
+ has_url_var = !is.null(url_var),
+ has_legend = !is.null(col_var) || !is.null(symbol_var) || !is.null(size_var),
+ has_tooltips = tooltips,
+ tooltip_text = tooltip_text,
+ has_custom_tooltips = !is.null(tooltip_text),
+ click_callback = htmlwidgets::JS(click_callback),
+ zoom_callback = htmlwidgets::JS(zoom_callback),
+ fixed = fixed,
+ legend_width = legend_width,
+ left_margin = left_margin,
+ html_id = html_id,
+ xlim = xlim,
+ ylim = ylim,
+ x_categorical = x_categorical,
+ y_categorical = y_categorical,
+ menu = menu,
+ lasso = lasso,
+ lasso_callback = htmlwidgets::JS(lasso_callback),
+ dom_id_reset_zoom = dom_id_reset_zoom,
+ dom_id_svg_export = dom_id_svg_export,
+ dom_id_lasso_toggle = dom_id_lasso_toggle,
+ transitions = transitions,
+ axes_font_size = axes_font_size,
+ legend_font_size = legend_font_size,
+ lines = lines,
+ hashes = hashes
+ )
+
+ ## pass the data and settings using 'x'
+ x <- list(
+ data = data,
+ settings = settings
+ )
+
+ ## create widget
+ htmlwidgets::createWidget(
+ name = 'scatterD3',
+ x,
+ width = width,
+ height = height,
+ package = 'scatterD3',
+ sizingPolicy = htmlwidgets::sizingPolicy(
+ browser.fill = TRUE,
+ viewer.fill = TRUE
+ )
+ )
}
#' @rdname scatterD3-shiny
#' @export
scatterD3Output <- function(outputId, width = '100%', height = '600px'){
- htmlwidgets::shinyWidgetOutput(outputId, 'scatterD3', width, height, package = 'scatterD3')
+ htmlwidgets::shinyWidgetOutput(outputId, 'scatterD3', width, height, package = 'scatterD3')
}
#' @rdname scatterD3-shiny
#' @export
renderScatterD3 <- function(expr, env = parent.frame(), quoted = FALSE) {
- if (!quoted) { expr <- substitute(expr) } # force quoted
- htmlwidgets::shinyRenderWidget(expr, scatterD3Output, env, quoted = TRUE)
+ if (!quoted) { expr <- substitute(expr) } # force quoted
+ htmlwidgets::shinyRenderWidget(expr, scatterD3Output, env, quoted = TRUE)
}
diff --git a/README.md b/README.md
index 5f00635..4ce994c 100644
--- a/README.md
+++ b/README.md
@@ -1,30 +1,22 @@
-`scatterD3` is an HTML R widget for interactive scatter plots visualization. It is based on the [htmlwidgets](http://www.htmlwidgets.org/) R package and on the [d3.js](http://d3js.org/) javascript
-library.
+`scatterD3` is an HTML R widget for interactive scatter plots visualization.
+It is based on the [htmlwidgets](http://www.htmlwidgets.org/) R package and on
+the [d3.js](http://d3js.org/) javascript library.
-![CRAN Downloads](http://cranlogs.r-pkg.org/badges/last-month/scatterD3)
+![CRAN Downloads](http://cranlogs.r-pkg.org/badges/last-month/scatterD3)
[![Travis-CI Build Status](https://travis-ci.org/juba/scatterD3.svg?branch=master)](https://travis-ci.org/juba/scatterD3)
-[![CRAN_Status_Badge](http://www.r-pkg.org/badges/version/scatterD3)](http://cran.r-project.org/package=scatterD3)
+[![CRAN_Status_Badge](http://www.r-pkg.org/badges/version/scatterD3)](https://cran.r-project.org/package=scatterD3)
## Features
-`scatterD3` currently provides the following features :
-
-- Display points and text labels
-- Possibility to map color, symbol and size with other variables (automatic legend)
-- Zoom with mouse wheel, pan with mouse while zoomed in
-- Ability to drag and move text labels
-- Customizable tooltips when hovering points
-- Points highlighting when hovering legend items
-- Option to draw confidence ellipses around group of points
-- Charts integrated inside a Shiny app are fully updatable with smooth transitions when settings or data change
-- Lasso selection tool integration via d3-lasso-plugin for points highlighting
-
-
Here is a small preview of what you will get :
-![example](https://raw.github.com/juba/scatterD3/master/resources/scatterD3.gif)
+![example](https://raw.github.com/juba/scatterD3/master/resources/scatterD3.gif)
-You can also test it live with the [sample shiny app](http://data.nozav.org/app/scatterD3/).
+Take a look at
+the
+[visual guide](https://rawgit.com/juba/scatterD3/master/vignettes%2Fintroduction.html#open-urls-when-clicking-points) for
+a list of features and examples. You can also test it live with
+the [sample shiny app](http://data.nozav.org/app/scatterD3/).
## Installation
@@ -36,39 +28,45 @@ Install latest stable release from CRAN :
Or from Github for the latest, bleeding edge, full of bugs version :
devtools::install_github("juba/scatterD3")
-
+
## Usage
Quick example of the `scatterD3` function based on the `mtcars` dataset :
-```R
-scatterD3(x = mtcars$wt, y = mtcars$mpg, lab = rownames(mtcars),
- col_var=mtcars$cyl, symbol_var = mtcars$am,
+```{r}
+mtcars$names <- rownames(mtcars)
+scatterD3(data = mtcars, x = wt, y = mpg, lab = names,
+ col_var = cyl, symbol_var = am,
xlab = "Weight", ylab = "Mpg", col_lab = "Cylinders",
symbol_lab = "Manual transmission")
```
-
-See [the introduction vignette](https://rawgit.com/juba/scatterD3/master/vignettes%2Fintroduction.html) for a step-by-step guide and details about the different function arguments.
+
+
+See [the visual guide](https://rawgit.com/juba/scatterD3/master/vignettes%2Fintroduction.html) for a step-by-step guide and details about the different function arguments.
## Shiny integration
-Like every R HTML widget, shiny integration is straightforward. But as a D3 widget, `scatterD3` is *updatable* : changes in settings or data can be displayed via smooth transitions instead of a complete chart redraw, which can provide interesting visual clues.
+Like every R HTML widget, shiny integration is straightforward. But as a D3
+widget, `scatterD3` is *updatable* : changes in settings or data can be
+displayed via smooth transitions instead of a complete chart redraw, which can
+provide interesting visual clues.
-Furthermore, `scatterD3` provides some additional handlers to two interactive features : SVG export and zoom resetting.
+Furthermore, `scatterD3` provides some additional handlers and callback hooks
+for a more complete JavaScript interactivity and integration.
-The
-[sample scatterD3 shiny app](http://data.nozav.org/app/scatterD3/) allows you to see the different features described here. You can [check its source code on GitHub](https://github.com/juba/scatterD3_shiny_app) and the [introduction vignette](https://rawgit.com/juba/scatterD3/master/vignettes%2Fintroduction.html) for a better understanding of the different arguments.
+The [sample scatterD3 shiny app](http://data.nozav.org/app/scatterD3/) allows
+you to see the different features described here. You
+can [check its source code on GitHub](https://github.com/juba/scatterD3_shiny_app)
+and the [visual guide](https://rawgit.com/juba/scatterD3/master/vignettes%2Fintroduction.html) for
+a better understanding of the different arguments.
## Credits
This package has been made possible by :
-- Michael Bostock's incredible [d3.js](https://d3js.org/) library and documentation
-- RStudio's [shiny](http://shiny.rstudio.com/) and [htmlwidgets](http://www.htmlwidgets.org/) packages
-- Susie Lu's [d3-legend](https://github.com/susielu/d3-legend) module
-- Rob Moore's [article on reusable d3.js charts](http://www.toptal.com/d3-js/towards-reusable-d3-js-charts)
-- Speros Kokenes' [d3 lasso plugin](https://github.com/skokenes/D3-Lasso-Plugin)
-
-
-
+- Michael Bostock's incredible [d3.js](https://d3js.org/) library and documentation
+- RStudio's [shiny](http://shiny.rstudio.com/) and [htmlwidgets](http://www.htmlwidgets.org/) packages
+- Susie Lu's [d3-legend](https://github.com/susielu/d3-legend) module
+- Rob Moore's [article on reusable d3.js charts](http://www.toptal.com/d3-js/towards-reusable-d3-js-charts)
+- Speros Kokenes' [d3 lasso plugin](https://github.com/skokenes/D3-Lasso-Plugin)
diff --git a/build/vignette.rds b/build/vignette.rds
index 96b0d9e..90fbe7d 100644
Binary files a/build/vignette.rds and b/build/vignette.rds differ
diff --git a/inst/doc/introduction.R b/inst/doc/introduction.R
index cf58e71..9eda912 100644
--- a/inst/doc/introduction.R
+++ b/inst/doc/introduction.R
@@ -1,46 +1,148 @@
-## ----basic---------------------------------------------------------------
+## ---- include=FALSE------------------------------------------------------
library(scatterD3)
-scatterD3(x = mtcars$wt, y = mtcars$mpg)
+
+## ----basic, eval=FALSE---------------------------------------------------
+# library(scatterD3)
+# scatterD3(x = mtcars$wt, y = mtcars$mpg)
+
+## ----basic_nse-----------------------------------------------------------
+scatterD3(data = mtcars , x = wt, y = mpg)
## ----basic_cust----------------------------------------------------------
-scatterD3(x = mtcars$wt, y = mtcars$mpg, point_size = 15, point_opacity = 0.5, fixed = TRUE)
+scatterD3(data = mtcars, x = wt, y = mpg,
+ point_size = 35, point_opacity = 0.5, fixed = TRUE,
+ colors = "#A94175")
+
+## ----hover_cust----------------------------------------------------------
+scatterD3(data = mtcars, x = wt, y = mpg,
+ point_size = 100, point_opacity = 0.5,
+ hover_size = 4, hover_opacity = 1)
+
+## ----categorical---------------------------------------------------------
+mtcars$cyl_fac <- paste(mtcars$cyl, "cylinders")
+scatterD3(data = mtcars, x = cyl_fac, y = mpg)
+
+## ----categorical_left_margin---------------------------------------------
+scatterD3(data = mtcars, x = wt, y = cyl_fac, left_margin = 80)
## ----labels--------------------------------------------------------------
-scatterD3(x = mtcars$wt, y = mtcars$mpg, lab = rownames(mtcars), labels_size = 9)
+mtcars$names <- rownames(mtcars)
+scatterD3(data = mtcars, x = wt, y = mpg, lab = names, labels_size = 9)
## ----mapping-------------------------------------------------------------
-scatterD3(x = mtcars$wt, y = mtcars$mpg, col_var = mtcars$cyl, symbol_var = mtcars$gear)
+scatterD3(data = mtcars, x = wt, y = mpg, col_var = cyl, symbol_var = gear)
## ----map_size------------------------------------------------------------
-scatterD3(x = mtcars$wt, y = mtcars$mpg, col_var = mtcars$cyl, size_var = mtcars$hp,
+scatterD3(data = mtcars, x = wt, y = mpg, col_var = cyl, size_var = hp,
size_range = c(10,1000), point_opacity = 0.7)
+## ----map_custom_colors---------------------------------------------------
+scatterD3(data = mtcars, x = wt, y = mpg, col_var = cyl,
+ colors = c("4" = "#ECD078", "8" = "#C02942", "6" = "#53777A"))
+
+## ----map_continuous_color------------------------------------------------
+scatterD3(data = mtcars, x = wt, y = mpg, col_var = disp)
+
+## ----opacity_var---------------------------------------------------------
+scatterD3(data=mtcars, x=mpg, y=wt, opacity_var = drat)
+
+## ----lines---------------------------------------------------------------
+scatterD3(data = mtcars, x = wt, y = mpg, fixed = TRUE,
+ lines = data.frame(slope = 1, intercept = 0))
+
+## ----lines_style---------------------------------------------------------
+scatterD3(data = mtcars, x = wt, y = mpg,
+ lines = data.frame(slope = 0,
+ intercept = 30,
+ stroke = "red",
+ stroke_width = 5,
+ stroke_dasharray = "10,5"))
+
+## ----lines_default-------------------------------------------------------
+scatterD3(data = mtcars, x = wt, y = mpg, fixed = TRUE,
+ lines = data.frame(slope = c(0, Inf),
+ intercept = c(0, 0),
+ stroke = "#000",
+ stroke_width = 1,
+ stroke_dasharray = 5))
+
## ----axis_limits---------------------------------------------------------
-scatterD3(x = mtcars$wt, y = mtcars$mpg, xlim=c(0,10), ylim=c(10,35))
+scatterD3(data = mtcars, x = wt, y = mpg, xlim=c(0,10), ylim=c(10,35))
## ----cust_labels---------------------------------------------------------
-scatterD3(x = mtcars$wt, y = mtcars$mpg, col_var = mtcars$cyl, symbol_var = mtcars$gear,
+scatterD3(data = mtcars, x = wt, y = mpg, col_var = cyl, symbol_var = gear,
xlab = "Weight", ylab = "Mpg", col_lab = "Cylinders", symbol_lab = "Gears")
+## ----cust_labels_size----------------------------------------------------
+scatterD3(data = mtcars, x = wt, y = mpg, col_var = cyl,
+ xlab = "Weight", ylab = "Mpg",
+ axes_font_size = "120%",
+ legend_font_size = "14px")
+
## ----cust_tooltips-------------------------------------------------------
tooltips <- paste("This is an incredible <strong>", rownames(mtcars),"</strong><br />with ",
mtcars$cyl, "cylinders !")
-scatterD3(x = mtcars$wt, y = mtcars$mpg, tooltip_text = tooltips)
+scatterD3(data = mtcars, x = wt, y = mpg, tooltip_text = tooltips)
+
+## ----urls----------------------------------------------------------------
+mtcars$urls <- paste0("https://www.duckduckgo.com/?q=", rownames(mtcars))
+scatterD3(data = mtcars, x = wt, y = mpg, lab = names, url_var = urls)
+
+## ----click_callback------------------------------------------------------
+scatterD3(data = mtcars, x = wt, y = mpg,
+ click_callback = "function(id, index) {
+ alert('scatterplot ID: ' + id + ' - Point index: ' + index)
+ }")
+
+## ---- click_callback_shiny, eval=FALSE-----------------------------------
+# scatterD3(data = mtcars, x = wt, y = mpg,
+# click_callback = "function(id, index) {
+# if(id && typeof(Shiny) != 'undefined') {
+# Shiny.onInputChange(id + '_selected', index);
+# }
+# }")
+
+## ----zoom_callback-------------------------------------------------------
+scatterD3(data = mtcars, x = wt, y = mpg,
+ zoom_callback = "function(xmin, xmax, ymin, ymax) {
+ var zoom = '<strong>Zoom</strong><br />xmin = ' + xmin + '<br />xmax = ' + xmax + '<br />ymin = ' + ymin + '<br />ymax = ' + ymax;
+ document.getElementById('zoomExample').innerHTML = zoom;
+ }")
## ----ellipses------------------------------------------------------------
-scatterD3(x = mtcars$wt, y = mtcars$mpg, ellipses = TRUE)
+scatterD3(data = mtcars, x = wt, y = mpg, ellipses = TRUE)
## ----ellipses_col--------------------------------------------------------
-scatterD3(x = mtcars$wt, y = mtcars$mpg, col_var = mtcars$cyl, ellipses = TRUE)
+scatterD3(data = mtcars, x = wt, y = mpg, col_var = cyl, ellipses = TRUE)
+
+## ----nomenu--------------------------------------------------------------
+scatterD3(data = mtcars, x = wt, y = mpg, menu = FALSE)
## ----lasso---------------------------------------------------------------
-scatterD3(x = mtcars$wt, y = mtcars$mpg, lab = rownames(mtcars), lasso = TRUE)
+mtcars$names <- rownames(mtcars)
+scatterD3(data = mtcars, x = wt, y = mpg, lab = names, lasso = TRUE)
## ----lasso_callback------------------------------------------------------
-scatterD3(x = mtcars$wt, y = mtcars$mpg, lab = rownames(mtcars),
+mtcars$names <- rownames(mtcars)
+scatterD3(data = mtcars,
+ x = wt, y = mpg, lab = names,
lasso = TRUE,
lasso_callback = "function(sel) {alert(sel.data().map(function(d) {return d.lab}).join('\\n'));}")
+## ----labels_export-------------------------------------------------------
+mtcars$names <- rownames(mtcars)
+scatterD3(data = mtcars, x = wt, y = mpg, lab = names)
+
+## ----labels_export_ggplot2, eval = FALSE---------------------------------
+# labels <- read.csv("scatterD3_labels.csv")
+# library(ggplot2)
+# ggplot() +
+# geom_point(data = mtcars, aes(x=wt, y=mpg)) +
+# geom_text(data = labels,
+# aes(x = scatterD3_label_x,
+# y = scatterD3_label_y,
+# label = scatterD3_label))
+
## ----cust_arrows---------------------------------------------------------
scatterD3(x = c(1, 0.9, 0.7, 0.2, -0.4, -0.5), xlab = "x",
y = c(1, 0.1, -0.5, 0.5, -0.6, 0.7), ylab = "y",
diff --git a/inst/doc/introduction.Rmd b/inst/doc/introduction.Rmd
index 0ccc163..5354a6d 100644
--- a/inst/doc/introduction.Rmd
+++ b/inst/doc/introduction.Rmd
@@ -1,5 +1,5 @@
---
-title: "Interactive scatterplots with scatterD3"
+title: "scatterD3 : a Visual Guide"
author: "Julien Barnier"
date: "`r Sys.Date()`"
output:
@@ -7,51 +7,95 @@ output:
fig_width: 5
toc: true
vignette: >
- %\VignetteIndexEntry{Introduction}
+ %\VignetteIndexEntry{scatterD3 : A Visual Guide}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
+```{r, include=FALSE}
+library(scatterD3)
+```
+
+
The `scatterD3` package provides an HTML widget based on the `htmlwidgets` package and allows to produce interactive scatterplots by using the `d3.js` javascript visualization library.
## Basic scatterplot
Starting with the sample `mtcars` dataset, we can produce a basic scatterplot with the following command :
-```{r basic}
+```{r basic, eval=FALSE}
library(scatterD3)
scatterD3(x = mtcars$wt, y = mtcars$mpg)
```
+You can pass data arguments as vectors, like above, but you can also give a data frame as `data` argument and then provide variable names which will be evaluated inside this data frame :
+
+```{r basic_nse}
+scatterD3(data = mtcars , x = wt, y = mpg)
+```
+
+
This will display a simple visualization with the given variables as `x` and `y` axis. There are several interactive features directly available :
- you can zoom in and out with the mouse wheel while the mouse cursor is on the plot
- you can pan the plot by dragging with your mouse
- by hovering over a point, you can display a small tooltip window giving the `x` and `y` values
-You can customize the points size with the `point_size` parameter, their opacity with `point_opacity`, and you can force the plot to have a 1:1 fixed aspect ratio with `fixed = TRUE`.
+You can customize the points size with the `point_size` parameter, their
+global opacity with `point_opacity`, and you can force the plot to have a 1:1
+fixed aspect ratio with `fixed = TRUE`. You can also manually specify the
+points color with the `colors` argument
```{r basic_cust}
-scatterD3(x = mtcars$wt, y = mtcars$mpg, point_size = 15, point_opacity = 0.5, fixed = TRUE)
+scatterD3(data = mtcars, x = wt, y = mpg,
+ point_size = 35, point_opacity = 0.5, fixed = TRUE,
+ colors = "#A94175")
+```
+
+You can change size and opacity of points when hovering with the `hover_size` and `hover_opacity` settings :
+
+```{r hover_cust}
+scatterD3(data = mtcars, x = wt, y = mpg,
+ point_size = 100, point_opacity = 0.5,
+ hover_size = 4, hover_opacity = 1)
```
+## Categorical `x` and `y`
+
+If the `x` or `y` variable is not numeric or is a factor, then an ordinal
+scale is used for the corresponding axis. Note that zooming is then not
+possible along this axis.
+
+```{r categorical}
+mtcars$cyl_fac <- paste(mtcars$cyl, "cylinders")
+scatterD3(data = mtcars, x = cyl_fac, y = mpg)
+```
+
+You can use the `left_margin` argument when using a categorical `y` variable
+if the axis labels are not entirely visible :
+
+```{r categorical_left_margin}
+scatterD3(data = mtcars, x = wt, y = cyl_fac, left_margin = 80)
+```
+
+
## Point labels
You can add text labels to the points by passing a character vector to the `lab` parameter. Labels size are controlled by the `labels_size` parameter.
```{r labels}
-scatterD3(x = mtcars$wt, y = mtcars$mpg, lab = rownames(mtcars), labels_size = 9)
+mtcars$names <- rownames(mtcars)
+scatterD3(data = mtcars, x = wt, y = mpg, lab = names, labels_size = 9)
```
Note that text labels are fully movable : click and drag a label with your mouse to place it where you want. Custom positions are preserved while zooming/panning.
-
-## Mapping colors, symbols and size to variables
+## Mapping colors, symbols, size and opacity to variables
By passing vectors to the `col_var` and/or `symbol_var` arguments, you can map points colors and symbols to other variables.
```{r mapping}
-scatterD3(x = mtcars$wt, y = mtcars$mpg, col_var = mtcars$cyl, symbol_var = mtcars$gear)
+scatterD3(data = mtcars, x = wt, y = mpg, col_var = cyl, symbol_var = gear)
```
A legend is then automatically added. You can manually specify its width with the `legend_width` argument. Use `legend_width = 0` to disable it entirely.
@@ -61,31 +105,101 @@ Note that when hovering over a legend item with your mouse, the corresponding po
You can also map symbol sizes with a variable with the `size_var` argument. `size_range` allows to customize the sizes range :
```{r map_size}
-scatterD3(x = mtcars$wt, y = mtcars$mpg, col_var = mtcars$cyl, size_var = mtcars$hp,
+scatterD3(data = mtcars, x = wt, y = mpg, col_var = cyl, size_var = hp,
size_range = c(10,1000), point_opacity = 0.7)
```
+You can specify custom colors by passing a vector of hexadecimal strings to the `colors` argument. If the vector is named, then the colors will be associated with their names within `col_var`.
+
+```{r map_custom_colors}
+scatterD3(data = mtcars, x = wt, y = mpg, col_var = cyl,
+ colors = c("4" = "#ECD078", "8" = "#C02942", "6" = "#53777A"))
+```
+
+If `col_var` is numeric, not a factor, and has more than 6 unique values, it
+is considered as continuous, and drawn accordingly using the Veridis d3
+interpolator.
+
+```{r map_continuous_color}
+scatterD3(data = mtcars, x = wt, y = mpg, col_var = disp)
+```
+
+In this case, any `colors` argument is ignored. You can force `col_var` to be considered as continuous with `col_continuous = TRUE`.
+
+You can also use the `opacity_var` argument to map point opacity to a variable.
+Note that for now no legend for opacity is added, though.
+
+```{r opacity_var}
+scatterD3(data=mtcars, x=mpg, y=wt, opacity_var = drat)
+```
+
+## Adding lines
+
+In addition to your data points, you can add to your scatterplot. This is done vy passing a *data frame* to the `lines` argument. This *data frame* must have at least two columns called `slope` and `intercept`, and as many rows as lines you want to draw.
+
+For example, if you want to add a 1:1 line :
+
+```{r lines}
+scatterD3(data = mtcars, x = wt, y = mpg, fixed = TRUE,
+ lines = data.frame(slope = 1, intercept = 0))
+```
+
+You can style your lines by adding `stroke`, `stroke_width` and `stroke_dasharray` columns. These columns values will be added as [corresponding styles](https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Fills_and_Strokes) to the generated SVG line. So if you want a wide dashed red horizontal line :
+
+```{r lines_style}
+scatterD3(data = mtcars, x = wt, y = mpg,
+ lines = data.frame(slope = 0,
+ intercept = 30,
+ stroke = "red",
+ stroke_width = 5,
+ stroke_dasharray = "10,5"))
+```
+
+If you want to draw a vertical line, pass the `Inf` value to `slope`. The value of `intercept` is then interpreted as the intercept along the x axis.
+
+By default, if no `lines` argument is provided two dashed horizontal and vertical lines are drawn through the origin, which is equivalent to :
+
+```{r lines_default}
+scatterD3(data = mtcars, x = wt, y = mpg, fixed = TRUE,
+ lines = data.frame(slope = c(0, Inf),
+ intercept = c(0, 0),
+ stroke = "#000",
+ stroke_width = 1,
+ stroke_dasharray = 5))
+```
+
+
## Axis limits
You can manually specify the `x` or `y` axis limits with the `xlim` and `ylim` arguments :
```{r axis_limits}
-scatterD3(x = mtcars$wt, y = mtcars$mpg, xlim=c(0,10), ylim=c(10,35))
+scatterD3(data = mtcars, x = wt, y = mpg, xlim=c(0,10), ylim=c(10,35))
```
+## Axes and legend customization
-## Custom axis and legend labels
-
-You can customize the axis and legend labels with `xlab`, `ylab`, `col_lab`, `symbol_lab` and `size_lab` :
+You can customize the value of the axes and legend labels with `xlab`, `ylab`, `col_lab`, `symbol_lab` and `size_lab` :
```{r cust_labels}
-scatterD3(x = mtcars$wt, y = mtcars$mpg, col_var = mtcars$cyl, symbol_var = mtcars$gear,
+scatterD3(data = mtcars, x = wt, y = mpg, col_var = cyl, symbol_var = gear,
xlab = "Weight", ylab = "Mpg", col_lab = "Cylinders", symbol_lab = "Gears")
```
Note that default tooltips are updated accordingly.
+You can also change the font size of axes and legend text with `axes_font_size` and `legend_font_size` :
+
+```{r cust_labels_size}
+scatterD3(data = mtcars, x = wt, y = mpg, col_var = cyl,
+ xlab = "Weight", ylab = "Mpg",
+ axes_font_size = "120%",
+ legend_font_size = "14px")
+```
+
+You can provide any CSS compatible value, wether a fixed size such as `2em` or a relative one like `95%`.
+
## Custom tooltips
@@ -94,33 +208,100 @@ If the default tooltips don't suit your needs, you can customize them by providi
```{r cust_tooltips}
tooltips <- paste("This is an incredible <strong>", rownames(mtcars),"</strong><br />with ",
mtcars$cyl, "cylinders !")
-scatterD3(x = mtcars$wt, y = mtcars$mpg, tooltip_text = tooltips)
+scatterD3(data = mtcars, x = wt, y = mpg, tooltip_text = tooltips)
```
You can also disable tooltips entirely with `tooltips = FALSE`.
+## Open URLs when clicking points
+
+With the `url_var` argument, you can specify a character vectors of URLs, associated to each point, and which will be opened when the point is clicked.
+
+```{r urls}
+mtcars$urls <- paste0("https://www.duckduckgo.com/?q=", rownames(mtcars))
+scatterD3(data = mtcars, x = wt, y = mpg, lab = names, url_var = urls)
+```
+
+Note that this won't work inside RStudio's internal browser.
+
+## JavaScript callback on clicking point
+
+The optional `click_callback` argument is a character string defining a JavaScript function to be called when a dot is clicked. It must accept two arguments : `html_id` (the unique `id` of the current scatterplot), and `i` (the index of the clicked point).
+
+```{r click_callback}
+scatterD3(data = mtcars, x = wt, y = mpg,
+ click_callback = "function(id, index) {
+ alert('scatterplot ID: ' + id + ' - Point index: ' + index)
+ }")
+```
+
+
+One usage can be to pass the index of the clicked point back to Shiny when `scatterD3` is run inside a Shiny app. The following implementation can do it by using `Shiny.onInputChange()` :
+
+```{r, click_callback_shiny, eval=FALSE}
+scatterD3(data = mtcars, x = wt, y = mpg,
+ click_callback = "function(id, index) {
+ if(id && typeof(Shiny) != 'undefined') {
+ Shiny.onInputChange(id + '_selected', index);
+ }
+}")
+```
+
+Thanks to [detule](https://github.com/detule) and [harveyl888](https://github.com/harveyl888) for the code.
+
+Note that `url_var` and `click_callback` cannot be used at the same time.
+
+
+## JavaScript zoom callback
+
+The optional `zoom_callback` argument is a character string defining a JavaScript function to be called when a zoom event is triggered. It must accept two arguments `xmin`, `xmax`, `ymin` and `ymax` (in this order), which give the new `x` and `y` domains after zooming.
+
+```{r zoom_callback}
+scatterD3(data = mtcars, x = wt, y = mpg,
+ zoom_callback = "function(xmin, xmax, ymin, ymax) {
+ var zoom = '<strong>Zoom</strong><br />xmin = ' + xmin + '<br />xmax = ' + xmax + '<br />ymin = ' + ymin + '<br />ymax = ' + ymax;
+ document.getElementById('zoomExample').innerHTML = zoom;
+ }")
+```
+
+<div id="zoomExample" style="font-size: 80%; background-color: #F9F9F9; padding: 5px; margin-left: 5em; width: 15em;"><strong>Zoom</strong><br /> None yet !</div>
+
+
+
## Confidence ellipses
You can draw a confidence ellipse around the points :
```{r ellipses}
-scatterD3(x = mtcars$wt, y = mtcars$mpg, ellipses = TRUE)
+scatterD3(data = mtcars, x = wt, y = mpg, ellipses = TRUE)
```
Or around the different groups of points defined by `col_var` :
```{r ellipses_col}
-scatterD3(x = mtcars$wt, y = mtcars$mpg, col_var = mtcars$cyl, ellipses = TRUE)
+scatterD3(data = mtcars, x = wt, y = mpg, col_var = cyl, ellipses = TRUE)
```
Ellipses are computed by the `ellipse.default()` function of the [ellipse package](https://cran.r-project.org/package=ellipse). The confidence level can be changed with the `ellipse_level` argument (`0.95` by default).
+## Gear menu
+
+The "gear menu" is a small menu which can be displayed by clocking on the "gear" icon on the top-right corner of the plot. It allows to reset the zoom, export the current graph to SVG, and toggle lasso selection.
+
+It is displayed by default, but you can hide it with the `menu = FALSE` argument.
+
+```{r nomenu}
+scatterD3(data = mtcars, x = wt, y = mpg, menu = FALSE)
+```
+
+
## Lasso selection tool
Thanks to the [d3-lasso-plugin](https://github.com/skokenes/D3-Lasso-Plugin) integration made by @[timelyportfolio](https://github.com/timelyportfolio), you can select and highlight points with a lasso selection tool. To activate it, just add a `lasso = TRUE` argument. The tool is used by shift-clicking and dragging on the plot area (if it doesn't activate, click on the chart first to give it focus).
```{r lasso}
-scatterD3(x = mtcars$wt, y = mtcars$mpg, lab = rownames(mtcars), lasso = TRUE)
+mtcars$names <- rownames(mtcars)
+scatterD3(data = mtcars, x = wt, y = mpg, lab = names, lasso = TRUE)
```
To undo the selection, just shift-click again.
@@ -130,11 +311,38 @@ You can specify a custom JavaScript callback function to be called by passing it
Here is an example which shows an alert with selected point labels :
```{r lasso_callback}
-scatterD3(x = mtcars$wt, y = mtcars$mpg, lab = rownames(mtcars),
+mtcars$names <- rownames(mtcars)
+scatterD3(data = mtcars,
+ x = wt, y = mpg, lab = names,
lasso = TRUE,
lasso_callback = "function(sel) {alert(sel.data().map(function(d) {return d.lab}).join('\\n'));}")
```
+## Custom labels positions export
+
+The "gear menu" allows to export the current custom labels position as a CSV file for later reuse.
+
+For example, if you change the labels placement in the following plot :
+
+```{r labels_export}
+mtcars$names <- rownames(mtcars)
+scatterD3(data = mtcars, x = wt, y = mpg, lab = names)
+```
+
+You can then open the menu and select *Export labels positions* to save them into a CSV file. If you want to use these positions in another plot, you can do something like that :
+
+```{r labels_export_ggplot2, eval = FALSE}
+labels <- read.csv("scatterD3_labels.csv")
+library(ggplot2)
+ggplot() +
+ geom_point(data = mtcars, aes(x=wt, y=mpg)) +
+ geom_text(data = labels,
+ aes(x = scatterD3_label_x,
+ y = scatterD3_label_y,
+ label = scatterD3_label))
+```
+
+
## Other options
@@ -162,14 +370,15 @@ Enabling transitions in your shiny app is quite simple, you just have to add the
### Additional controls : Reset zoom and SVG export
-Furthermore, `scatterD3` provides some additional handlers for two interactive features : SVG export and zoom resetting.
+Furthermore, `scatterD3` provides some additional handlers for three interactive features : SVG export, zoom resetting and lasso selection. Those are already accessible via the "gear menu", but you may want to replace it with custom form controls.
By default, you just have to give the following `id` to the corresponding form controls :
- `#scatterD3-reset-zoom` : reset zoom to default on click
- `#scatterD3-svg-export` : link to download the currently displayed figure as an SVG file
+- `#scatterD3-lasso-toggle` : toggle lasso selection
-If you are not happy with these ids, you can specify their names yourself with the arguments `dom_id_svg_export` and `dom_id_reset_zoom`.
+If you are not happy with these ids, you can specify their names yourself with the arguments `dom_id_svg_export`, `dom_id_reset_zoom` and `dom_id_toggle`.
### Sample app and source code
diff --git a/inst/doc/introduction.html b/inst/doc/introduction.html
index ad452aa..1675d27 100644
--- a/inst/doc/introduction.html
+++ b/inst/doc/introduction.html
@@ -7,46 +7,92 @@
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="pandoc" />
+
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="author" content="Julien Barnier" />
-<meta name="date" content="2016-03-24" />
+<meta name="date" content="2016-10-26" />
-<title>Interactive scatterplots with scatterD3</title>
+<title>scatterD3 : a Visual Guide</title>
-<script src="data:application/x-javascript,%28function%28%29%20%7B%0A%20%20%2F%2F%20If%20window%2EHTMLWidgets%20is%20already%20defined%2C%20then%20use%20it%3B%20otherwise%20create%20a%0A%20%20%2F%2F%20new%20object%2E%20This%20allows%20preceding%20code%20to%20set%20options%20that%20affect%20the%0A%20%20%2F%2F%20initialization%20process%20%28though%20none%20currently%20exist%29%2E%0A%20%20window%2EHTMLWidgets%20%3D%20window%2EHTMLWidgets%20%7C%7C%20%7B%7D%3B%0A%0A%20%20%2F%2F%20See%20if%20 [...]
-<link href="data:text/css,%2EscatterD3%2Dtooltip%20%7B%0A%20%20%20%20position%3A%20absolute%3B%0A%20%20%20%20color%3A%20%23222%3B%0A%20%20%20%20background%3A%20%23fff%3B%0A%20%20%20%20padding%3A%20%2E5em%3B%0A%20%20%20%20text%2Dshadow%3A%20%23f5f5f5%200%201px%200%3B%0A%20%20%20%20border%2Dradius%3A%202px%3B%0A%20%20%20%20box%2Dshadow%3A%200px%200px%202px%200px%20%23a6a6a6%3B%0A%20%20%20%20opacity%3A%200%2E9%3B%0A%20%20%20%20font%2Dfamily%3A%20sans%2Dserif%3B%0A%20%20%20%20font%2Dsize%3A% [...]
-<script src="data:application/x-javascript,%21function%28%29%7Bfunction%20n%28n%29%7Breturn%20n%26%26%28n%2EownerDocument%7C%7Cn%2Edocument%7C%7Cn%29%2EdocumentElement%7Dfunction%20t%28n%29%7Breturn%20n%26%26%28n%2EownerDocument%26%26n%2EownerDocument%2EdefaultView%7C%7Cn%2Edocument%26%26n%7C%7Cn%2EdefaultView%29%7Dfunction%20e%28n%2Ct%29%7Breturn%20t%3En%3F%2D1%3An%3Et%3F1%3An%3E%3Dt%3F0%3A0%2F0%7Dfunction%20r%28n%29%7Breturn%20null%3D%3D%3Dn%3F0%2F0%3A%2Bn%7Dfunction%20u%28n%29%7Bretur [...]
-<script src="data:application/x-javascript,%21function%20a%28b%2Cc%2Cd%29%7Bfunction%20e%28g%2Ch%29%7Bif%28%21c%5Bg%5D%29%7Bif%28%21b%5Bg%5D%29%7Bvar%20i%3D%22function%22%3D%3Dtypeof%20require%26%26require%3Bif%28%21h%26%26i%29return%20i%28g%2C%210%29%3Bif%28f%29return%20f%28g%2C%210%29%3Bvar%20j%3Dnew%20Error%28%22Cannot%20find%20module%20%27%22%2Bg%2B%22%27%22%29%3Bthrow%20j%2Ecode%3D%22MODULE%5FNOT%5FFOUND%22%2Cj%7Dvar%20k%3Dc%5Bg%5D%3D%7Bexports%3A%7B%7D%7D%3Bb%5Bg%5D%5B0%5D%2Ecall%2 [...]
-<link href="data:text/css,%2Elasso%20path%20%7B%0A%20%20stroke%3A%20rgb%2880%2C80%2C80%29%3B%0A%20%20stroke%2Dwidth%3A%202px%3B%0A%7D%0A%0A%2Elasso%20%2Edrawn%20%7B%0A%20%20fill%3A%20%23CCCCCC%3B%0A%20%20fill%2Dopacity%3A%20%2E15%20%3B%0A%7D%0A%0A%2Elasso%20%2Eloop%5Fclose%20%7B%0A%20%20fill%3A%20none%3B%0A%20%20stroke%2Ddasharray%3A%204%2C4%3B%0A%7D%0A%0A%2Elasso%20%2Eorigin%20%7B%0A%20%20fill%3A%20%233399FF%3B%0A%20%20fill%2Dopacity%3A%20%2E5%3B%0A%7D%0A%0A%2EscatterD3%20%2Enot%2Dpossi [...]
-<script src="data:application/x-javascript,d3%2Elasso%20%3D%20function%28%29%20%7B%0A%0A%20%20%20%20var%20items%20%3D%20null%2C%0A%20%20%20%20%20%20%20%20closePathDistance%20%3D%2075%2C%0A%20%20%20%20%20%20%20%20closePathSelect%20%3D%20true%2C%0A%20%20%20%20%20%20%20%20isPathClosed%20%3D%20false%2C%0A%20%20%20%20%20%20%20%20hoverSelect%20%3D%20true%2C%0A%20%20%20%20%20%20%20%20points%20%3D%20%5B%5D%2C%0A%20%20%20%20%20%20%20%20area%20%3D%20null%2C%0A%20%20%20%20%20%20%20%20on%20%3D%20%7B [...]
-<script src="data:application/x-javascript,function%20scatterD3%28%29%20%7B%0A%0A%20%20%20%20var%20width%20%3D%20600%2C%20%2F%2F%20default%20width%0A%20%20%20%20height%20%3D%20600%2C%20%2F%2F%20default%20height%0A%20%20%20%20dims%20%3D%20%7B%7D%2C%0A%20%20%20%20margin%20%3D%20%7Btop%3A%205%2C%20right%3A%2010%2C%20bottom%3A%2020%2C%20left%3A%2050%2C%20legend%5Ftop%3A%2050%7D%2C%0A%20%20%20%20settings%20%3D%20%7B%7D%2C%0A%20%20%20%20data%20%3D%20%5B%5D%2C%0A%20%20%20%20x%2C%20y%2C%20color% [...]
+<script src="data:application/x-javascript;base64,KGZ1bmN0aW9uKCkgewogIC8vIElmIHdpbmRvdy5IVE1MV2lkZ2V0cyBpcyBhbHJlYWR5IGRlZmluZWQsIHRoZW4gdXNlIGl0OyBvdGhlcndpc2UgY3JlYXRlIGEKICAvLyBuZXcgb2JqZWN0LiBUaGlzIGFsbG93cyBwcmVjZWRpbmcgY29kZSB0byBzZXQgb3B0aW9ucyB0aGF0IGFmZmVjdCB0aGUKICAvLyBpbml0aWFsaXphdGlvbiBwcm9jZXNzICh0aG91Z2ggbm9uZSBjdXJyZW50bHkgZXhpc3QpLgogIHdpbmRvdy5IVE1MV2lkZ2V0cyA9IHdpbmRvdy5IVE1MV2lkZ2V0cyB8fCB7fTsKCiAgLy8gU2VlIGlmIHdlJ3JlIHJ1bm5pbmcgaW4gYSB2aWV3ZXIgcGFuZS4gSWYgbm90LCB3ZS [...]
+<script src="data:application/x-javascript;base64,Ly8gaHR0cHM6Ly9kM2pzLm9yZy9kMy1jb2xvci8gVmVyc2lvbiAxLjAuMS4gQ29weXJpZ2h0IDIwMTYgTWlrZSBCb3N0b2NrLgohZnVuY3Rpb24odCxlKXsib2JqZWN0Ij09dHlwZW9mIGV4cG9ydHMmJiJ1bmRlZmluZWQiIT10eXBlb2YgbW9kdWxlP2UoZXhwb3J0cyk6ImZ1bmN0aW9uIj09dHlwZW9mIGRlZmluZSYmZGVmaW5lLmFtZD9kZWZpbmUoWyJleHBvcnRzIl0sZSk6ZSh0LmQzPXQuZDN8fHt9KX0odGhpcyxmdW5jdGlvbih0KXsidXNlIHN0cmljdCI7ZnVuY3Rpb24gZSh0LGUsbil7dC5wcm90b3R5cGU9ZS5wcm90b3R5cGU9bixuLmNvbnN0cnVjdG9yPXR9ZnVuY3Rpb24gbi [...]
+<script src="data:application/x-javascript;base64,Ly8gaHR0cHM6Ly9kM2pzLm9yZy9kMy1hcnJheS8gVmVyc2lvbiAxLjAuMS4gQ29weXJpZ2h0IDIwMTYgTWlrZSBCb3N0b2NrLgohZnVuY3Rpb24obixyKXsib2JqZWN0Ij09dHlwZW9mIGV4cG9ydHMmJiJ1bmRlZmluZWQiIT10eXBlb2YgbW9kdWxlP3IoZXhwb3J0cyk6ImZ1bmN0aW9uIj09dHlwZW9mIGRlZmluZSYmZGVmaW5lLmFtZD9kZWZpbmUoWyJleHBvcnRzIl0scik6cihuLmQzPW4uZDN8fHt9KX0odGhpcyxmdW5jdGlvbihuKXsidXNlIHN0cmljdCI7ZnVuY3Rpb24gcihuLHIpe3JldHVybiBuPHI/LTE6bj5yPzE6bj49cj8wOk5hTn1mdW5jdGlvbiB0KG4pe3JldHVybiAxPT [...]
+<script src="data:application/x-javascript;base64,Ly8gaHR0cHM6Ly9kM2pzLm9yZy9kMy1jb2xsZWN0aW9uLyBWZXJzaW9uIDEuMC4xLiBDb3B5cmlnaHQgMjAxNiBNaWtlIEJvc3RvY2suCiFmdW5jdGlvbihuLHQpeyJvYmplY3QiPT10eXBlb2YgZXhwb3J0cyYmInVuZGVmaW5lZCIhPXR5cGVvZiBtb2R1bGU/dChleHBvcnRzKToiZnVuY3Rpb24iPT10eXBlb2YgZGVmaW5lJiZkZWZpbmUuYW1kP2RlZmluZShbImV4cG9ydHMiXSx0KTp0KG4uZDM9bi5kM3x8e30pfSh0aGlzLGZ1bmN0aW9uKG4peyJ1c2Ugc3RyaWN0IjtmdW5jdGlvbiB0KCl7fWZ1bmN0aW9uIGUobixlKXt2YXIgcj1uZXcgdDtpZihuIGluc3RhbmNlb2YgdCluLmVhY2 [...]
+<script src="data:application/x-javascript;base64,Ly8gaHR0cHM6Ly9kM2pzLm9yZy9kMy1mb3JtYXQvIFZlcnNpb24gMS4wLjIuIENvcHlyaWdodCAyMDE2IE1pa2UgQm9zdG9jay4KIWZ1bmN0aW9uKHQsbil7Im9iamVjdCI9PXR5cGVvZiBleHBvcnRzJiYidW5kZWZpbmVkIiE9dHlwZW9mIG1vZHVsZT9uKGV4cG9ydHMpOiJmdW5jdGlvbiI9PXR5cGVvZiBkZWZpbmUmJmRlZmluZS5hbWQ/ZGVmaW5lKFsiZXhwb3J0cyJdLG4pOm4odC5kMz10LmQzfHx7fSl9KHRoaXMsZnVuY3Rpb24odCl7InVzZSBzdHJpY3QiO2Z1bmN0aW9uIG4odCxuKXtpZigocj0odD1uP3QudG9FeHBvbmVudGlhbChuLTEpOnQudG9FeHBvbmVudGlhbCgpKS5pbm [...]
+<script src="data:application/x-javascript;base64,Ly8gaHR0cHM6Ly9kM2pzLm9yZy9kMy1kaXNwYXRjaC8gVmVyc2lvbiAxLjAuMS4gQ29weXJpZ2h0IDIwMTYgTWlrZSBCb3N0b2NrLgohZnVuY3Rpb24obixlKXsib2JqZWN0Ij09dHlwZW9mIGV4cG9ydHMmJiJ1bmRlZmluZWQiIT10eXBlb2YgbW9kdWxlP2UoZXhwb3J0cyk6ImZ1bmN0aW9uIj09dHlwZW9mIGRlZmluZSYmZGVmaW5lLmFtZD9kZWZpbmUoWyJleHBvcnRzIl0sZSk6ZShuLmQzPW4uZDN8fHt9KX0odGhpcyxmdW5jdGlvbihuKXsidXNlIHN0cmljdCI7ZnVuY3Rpb24gZSgpe2Zvcih2YXIgbixlPTAscj1hcmd1bWVudHMubGVuZ3RoLG89e307ZTxyOysrZSl7aWYoIShuPW [...]
+<script src="data:application/x-javascript;base64,Ly8gaHR0cHM6Ly9kM2pzLm9yZy9kMy1lYXNlLyBWZXJzaW9uIDEuMC4xLiBDb3B5cmlnaHQgMjAxNiBNaWtlIEJvc3RvY2suCiFmdW5jdGlvbihuLHQpeyJvYmplY3QiPT10eXBlb2YgZXhwb3J0cyYmInVuZGVmaW5lZCIhPXR5cGVvZiBtb2R1bGU/dChleHBvcnRzKToiZnVuY3Rpb24iPT10eXBlb2YgZGVmaW5lJiZkZWZpbmUuYW1kP2RlZmluZShbImV4cG9ydHMiXSx0KTp0KG4uZDM9bi5kM3x8e30pfSh0aGlzLGZ1bmN0aW9uKG4peyJ1c2Ugc3RyaWN0IjtmdW5jdGlvbiB0KG4pe3JldHVybitufWZ1bmN0aW9uIGUobil7cmV0dXJuIG4qbn1mdW5jdGlvbiB1KG4pe3JldHVybiBuKi [...]
+<script src="data:application/x-javascript;base64,Ly8gaHR0cHM6Ly9kM2pzLm9yZy9kMy1pbnRlcnBvbGF0ZS8gVmVyc2lvbiAxLjEuMS4gQ29weXJpZ2h0IDIwMTYgTWlrZSBCb3N0b2NrLgohZnVuY3Rpb24odCxuKXsib2JqZWN0Ij09dHlwZW9mIGV4cG9ydHMmJiJ1bmRlZmluZWQiIT10eXBlb2YgbW9kdWxlP24oZXhwb3J0cyxyZXF1aXJlKCJkMy1jb2xvciIpKToiZnVuY3Rpb24iPT10eXBlb2YgZGVmaW5lJiZkZWZpbmUuYW1kP2RlZmluZShbImV4cG9ydHMiLCJkMy1jb2xvciJdLG4pOm4odC5kMz10LmQzfHx7fSx0LmQzKX0odGhpcyxmdW5jdGlvbih0LG4peyJ1c2Ugc3RyaWN0IjtmdW5jdGlvbiByKHQsbixyLGUsbyl7dmFyIG [...]
+<script src="data:application/x-javascript;base64,Ly8gaHR0cHM6Ly9kM2pzLm9yZy9kMy1zZWxlY3Rpb24vIFZlcnNpb24gMS4wLjIuIENvcHlyaWdodCAyMDE2IE1pa2UgQm9zdG9jay4KIWZ1bmN0aW9uKHQsbil7Im9iamVjdCI9PXR5cGVvZiBleHBvcnRzJiYidW5kZWZpbmVkIiE9dHlwZW9mIG1vZHVsZT9uKGV4cG9ydHMpOiJmdW5jdGlvbiI9PXR5cGVvZiBkZWZpbmUmJmRlZmluZS5hbWQ/ZGVmaW5lKFsiZXhwb3J0cyJdLG4pOm4odC5kMz10LmQzfHx7fSl9KHRoaXMsZnVuY3Rpb24odCl7InVzZSBzdHJpY3QiO2Z1bmN0aW9uIG4odCl7dmFyIG49dCs9IiIsZT1uLmluZGV4T2YoIjoiKTtyZXR1cm4gZT49MCYmInhtbG5zIiE9PS [...]
+<script src="data:application/x-javascript;base64,Ly8gaHR0cHM6Ly9kM2pzLm9yZy9kMy1zY2FsZS8gVmVyc2lvbiAxLjAuMy4gQ29weXJpZ2h0IDIwMTYgTWlrZSBCb3N0b2NrLgohZnVuY3Rpb24oZSxuKXsib2JqZWN0Ij09dHlwZW9mIGV4cG9ydHMmJiJ1bmRlZmluZWQiIT10eXBlb2YgbW9kdWxlP24oZXhwb3J0cyxyZXF1aXJlKCJkMy1hcnJheSIpLHJlcXVpcmUoImQzLWNvbGxlY3Rpb24iKSxyZXF1aXJlKCJkMy1pbnRlcnBvbGF0ZSIpLHJlcXVpcmUoImQzLWZvcm1hdCIpLHJlcXVpcmUoImQzLXRpbWUiKSxyZXF1aXJlKCJkMy10aW1lLWZvcm1hdCIpLHJlcXVpcmUoImQzLWNvbG9yIikpOiJmdW5jdGlvbiI9PXR5cGVvZiBkZW [...]
+<script src="data:application/x-javascript;base64,Ly8gaHR0cHM6Ly9kM2pzLm9yZy9kMy10aW1lci8gVmVyc2lvbiAxLjAuMy4gQ29weXJpZ2h0IDIwMTYgTWlrZSBCb3N0b2NrLgohZnVuY3Rpb24odCxuKXsib2JqZWN0Ij09dHlwZW9mIGV4cG9ydHMmJiJ1bmRlZmluZWQiIT10eXBlb2YgbW9kdWxlP24oZXhwb3J0cyk6ImZ1bmN0aW9uIj09dHlwZW9mIGRlZmluZSYmZGVmaW5lLmFtZD9kZWZpbmUoWyJleHBvcnRzIl0sbik6bih0LmQzPXQuZDN8fHt9KX0odGhpcyxmdW5jdGlvbih0KXsidXNlIHN0cmljdCI7ZnVuY3Rpb24gbigpe3JldHVybiB4fHwoVChlKSx4PWIubm93KCkrdyl9ZnVuY3Rpb24gZSgpe3g9MH1mdW5jdGlvbiBpKC [...]
+<script src="data:application/x-javascript;base64,Ly8gaHR0cHM6Ly9kM2pzLm9yZy9kMy10cmFuc2l0aW9uLyBWZXJzaW9uIDEuMC4yLiBDb3B5cmlnaHQgMjAxNiBNaWtlIEJvc3RvY2suCiFmdW5jdGlvbih0LG4peyJvYmplY3QiPT10eXBlb2YgZXhwb3J0cyYmInVuZGVmaW5lZCIhPXR5cGVvZiBtb2R1bGU/bihleHBvcnRzLHJlcXVpcmUoImQzLXNlbGVjdGlvbiIpLHJlcXVpcmUoImQzLWRpc3BhdGNoIikscmVxdWlyZSgiZDMtdGltZXIiKSxyZXF1aXJlKCJkMy1pbnRlcnBvbGF0ZSIpLHJlcXVpcmUoImQzLWNvbG9yIikscmVxdWlyZSgiZDMtZWFzZSIpKToiZnVuY3Rpb24iPT10eXBlb2YgZGVmaW5lJiZkZWZpbmUuYW1kP2RlZm [...]
+<script src="data:application/x-javascript;base64,Ly8gaHR0cHM6Ly9kM2pzLm9yZy9kMy1kcmFnLyBWZXJzaW9uIDEuMC4xLiBDb3B5cmlnaHQgMjAxNiBNaWtlIEJvc3RvY2suCiFmdW5jdGlvbihlLHQpeyJvYmplY3QiPT10eXBlb2YgZXhwb3J0cyYmInVuZGVmaW5lZCIhPXR5cGVvZiBtb2R1bGU/dChleHBvcnRzLHJlcXVpcmUoImQzLWRpc3BhdGNoIikscmVxdWlyZSgiZDMtc2VsZWN0aW9uIikpOiJmdW5jdGlvbiI9PXR5cGVvZiBkZWZpbmUmJmRlZmluZS5hbWQ/ZGVmaW5lKFsiZXhwb3J0cyIsImQzLWRpc3BhdGNoIiwiZDMtc2VsZWN0aW9uIl0sdCk6dChlLmQzPWUuZDN8fHt9LGUuZDMsZS5kMyl9KHRoaXMsZnVuY3Rpb24oZS [...]
+<script src="data:application/x-javascript;base64,Ly8gaHR0cHM6Ly9kM2pzLm9yZy9kMy1wYXRoLyBWZXJzaW9uIDEuMC4xLiBDb3B5cmlnaHQgMjAxNiBNaWtlIEJvc3RvY2suCiFmdW5jdGlvbih0LHMpeyJvYmplY3QiPT10eXBlb2YgZXhwb3J0cyYmInVuZGVmaW5lZCIhPXR5cGVvZiBtb2R1bGU/cyhleHBvcnRzKToiZnVuY3Rpb24iPT10eXBlb2YgZGVmaW5lJiZkZWZpbmUuYW1kP2RlZmluZShbImV4cG9ydHMiXSxzKTpzKHQuZDM9dC5kM3x8e30pfSh0aGlzLGZ1bmN0aW9uKHQpeyJ1c2Ugc3RyaWN0IjtmdW5jdGlvbiBzKCl7dGhpcy5feDA9dGhpcy5feTA9dGhpcy5feDE9dGhpcy5feTE9bnVsbCx0aGlzLl89W119ZnVuY3Rpb2 [...]
+<script src="data:application/x-javascript;base64,Ly8gaHR0cHM6Ly9kM2pzLm9yZy9kMy1zaGFwZS8gVmVyc2lvbiAxLjAuMy4gQ29weXJpZ2h0IDIwMTYgTWlrZSBCb3N0b2NrLgohZnVuY3Rpb24odCxpKXsib2JqZWN0Ij09dHlwZW9mIGV4cG9ydHMmJiJ1bmRlZmluZWQiIT10eXBlb2YgbW9kdWxlP2koZXhwb3J0cyxyZXF1aXJlKCJkMy1wYXRoIikpOiJmdW5jdGlvbiI9PXR5cGVvZiBkZWZpbmUmJmRlZmluZS5hbWQ/ZGVmaW5lKFsiZXhwb3J0cyIsImQzLXBhdGgiXSxpKTppKHQuZDM9dC5kM3x8e30sdC5kMyl9KHRoaXMsZnVuY3Rpb24odCxpKXsidXNlIHN0cmljdCI7ZnVuY3Rpb24gbih0KXtyZXR1cm4gZnVuY3Rpb24oKXtyZX [...]
+<script src="data:application/x-javascript;base64,Ly8gaHR0cHM6Ly9kM2pzLm9yZy9kMy1heGlzLyBWZXJzaW9uIDEuMC4zLiBDb3B5cmlnaHQgMjAxNiBNaWtlIEJvc3RvY2suCiFmdW5jdGlvbih0LG4peyJvYmplY3QiPT10eXBlb2YgZXhwb3J0cyYmInVuZGVmaW5lZCIhPXR5cGVvZiBtb2R1bGU/bihleHBvcnRzKToiZnVuY3Rpb24iPT10eXBlb2YgZGVmaW5lJiZkZWZpbmUuYW1kP2RlZmluZShbImV4cG9ydHMiXSxuKTpuKHQuZDM9dC5kM3x8e30pfSh0aGlzLGZ1bmN0aW9uKHQpeyJ1c2Ugc3RyaWN0IjtmdW5jdGlvbiBuKHQpe3JldHVybiB0fWZ1bmN0aW9uIGUodCxuLGUpe3ZhciByPXQoZSk7cmV0dXJuInRyYW5zbGF0ZSgiKy [...]
+<script src="data:application/x-javascript;base64,IWZ1bmN0aW9uIGEoYixjLGQpe2Z1bmN0aW9uIGUoZyxoKXtpZighY1tnXSl7aWYoIWJbZ10pe3ZhciBpPSJmdW5jdGlvbiI9PXR5cGVvZiByZXF1aXJlJiZyZXF1aXJlO2lmKCFoJiZpKXJldHVybiBpKGcsITApO2lmKGYpcmV0dXJuIGYoZywhMCk7dmFyIGo9bmV3IEVycm9yKCJDYW5ub3QgZmluZCBtb2R1bGUgJyIrZysiJyIpO3Rocm93IGouY29kZT0iTU9EVUxFX05PVF9GT1VORCIsan12YXIgaz1jW2ddPXtleHBvcnRzOnt9fTtiW2ddWzBdLmNhbGwoay5leHBvcnRzLGZ1bmN0aW9uKGEpe3ZhciBjPWJbZ11bMV1bYV07cmV0dXJuIGUoYz9jOmEpfSxrLGsuZXhwb3J0cyxhLGIsYy [...]
+<script src="data:application/x-javascript;base64,Ly8gaHR0cHM6Ly9kM2pzLm9yZy9kMy16b29tLyBWZXJzaW9uIDEuMC4zLiBDb3B5cmlnaHQgMjAxNiBNaWtlIEJvc3RvY2suCiFmdW5jdGlvbih0LGUpeyJvYmplY3QiPT10eXBlb2YgZXhwb3J0cyYmInVuZGVmaW5lZCIhPXR5cGVvZiBtb2R1bGU/ZShleHBvcnRzLHJlcXVpcmUoImQzLWRpc3BhdGNoIikscmVxdWlyZSgiZDMtZHJhZyIpLHJlcXVpcmUoImQzLWludGVycG9sYXRlIikscmVxdWlyZSgiZDMtc2VsZWN0aW9uIikscmVxdWlyZSgiZDMtdHJhbnNpdGlvbiIpKToiZnVuY3Rpb24iPT10eXBlb2YgZGVmaW5lJiZkZWZpbmUuYW1kP2RlZmluZShbImV4cG9ydHMiLCJkMy1kaX [...]
+<link href="data:text/css;charset=utf-8,%2Elasso%20path%20%7B%0Astroke%3A%20rgb%2880%2C80%2C80%29%3B%0Astroke%2Dwidth%3A%202px%3B%0A%7D%0A%2Elasso%20%2Edrawn%20%7B%0Afill%3A%20%23CCCCCC%3B%0Afill%2Dopacity%3A%20%2E15%20%3B%0A%7D%0A%2Elasso%20%2Eloop%5Fclose%20%7B%0Afill%3A%20none%3B%0Astroke%2Ddasharray%3A%204%2C4%3B%0A%7D%0A%2Elasso%20%2Eorigin%20%7B%0Afill%3A%20%233399FF%3B%0Afill%2Dopacity%3A%20%2E5%3B%0A%7D%0A%2EscatterD3%20%2Enot%2Dpossible%2Dlasso%20%7B%0Afill%3A%20rgb%28150%2C150% [...]
+<script src="data:application/x-javascript;base64,ZDMubGFzc28gPSBmdW5jdGlvbigpIHsKCiAgICB2YXIgaXRlbXMgPSBudWxsLAogICAgICAgIGNsb3NlUGF0aERpc3RhbmNlID0gNzUsCiAgICAgICAgY2xvc2VQYXRoU2VsZWN0ID0gdHJ1ZSwKICAgICAgICBpc1BhdGhDbG9zZWQgPSBmYWxzZSwKICAgICAgICBob3ZlclNlbGVjdCA9IHRydWUsCiAgICAgICAgcG9pbnRzID0gW10sCiAgICAgICAgYXJlYSA9IG51bGwsCiAgICAgICAgb24gPSB7c3RhcnQ6ZnVuY3Rpb24oKXt9LCBkcmF3OiBmdW5jdGlvbigpe30sIGVuZDogZnVuY3Rpb24oKXt9fTsKCiAgICBmdW5jdGlvbiBsYXNzbyhzZWxlY3Rpb24pIHsKCiAgICAgICAgLy8gdG [...]
+<link href="data:text/css;charset=utf-8,%2EscatterD3%2Dtooltip%20%7B%0Aposition%3A%20absolute%3B%0Acolor%3A%20%23222%3B%0Abackground%3A%20%23fff%3B%0Apadding%3A%20%2E5em%3B%0Atext%2Dshadow%3A%20%23f5f5f5%200%201px%200%3B%0Aborder%2Dradius%3A%202px%3B%0Abox%2Dshadow%3A%200px%200px%207px%201px%20%23a6a6a6%3B%0Aopacity%3A%200%2E95%3B%0Afont%2Dfamily%3A%20Open%20Sans%2C%20Droid%20Sans%2C%20Helvetica%2C%20Verdana%2C%20sans%2Dserif%3B%0Afont%2Dsize%3A%2010px%3B%0Az%2Dindex%3A%2010%3B%0A%7D%0A% [...]
+<script src="data:application/x-javascript;base64,Ly8gQ2xlYW4gdmFyaWFibGVzIGxldmVscyB0byBiZSB2YWxpZCBDU1MgY2xhc3NlcwpmdW5jdGlvbiBjc3NfY2xlYW4ocykgewogICAgaWYgKHMgPT09IHVuZGVmaW5lZCkgcmV0dXJuICIiOwogICAgcmV0dXJuIHMudG9TdHJpbmcoKS5yZXBsYWNlKC9bXlx3LV0vZywgIl8iKTsKfQoKLy8gRGVmYXVsdCB0cmFuc2xhdGlvbiBmdW5jdGlvbiBmb3IgcG9pbnRzIGFuZCBsYWJlbHMKZnVuY3Rpb24gdHJhbnNsYXRpb24oZCwgc2NhbGVzKSB7CiAgICAgcmV0dXJuICJ0cmFuc2xhdGUoIiArIHNjYWxlcy54KGQueCkgKyAiLCIgKyBzY2FsZXMueShkLnkpICsgIikiOwp9CgovLyBDcmVhdG [...]
+<script src="data:application/x-javascript;base64,Ly8gQ3VzdG9tIGNvbG9yIHNjaGVtZQpmdW5jdGlvbiBjdXN0b21fc2NoZW1lMTAgKCkgewogICAgLy8gc2xpY2UoKSB0byBjcmVhdGUgYSBjb3B5CiAgICB2YXIgc2NoZW1lID0gZDMuc2NoZW1lQ2F0ZWdvcnkxMC5zbGljZSgpOwogICAgLy8gU3dpdGNoIG9yYW5nZSBhbmQgcmVkCiAgICB2YXIJdG1wID0gc2NoZW1lWzNdOwogICAgc2NoZW1lWzNdID0gc2NoZW1lWzFdOwogICAgc2NoZW1lWzFdID0gdG1wOwogICAgcmV0dXJuIHNjaGVtZTsKfQoKLy8gU2V0dXAgZGltZW5zaW9ucwpmdW5jdGlvbiBzZXR1cF9zaXplcyAod2lkdGgsIGhlaWdodCwgc2V0dGluZ3MpIHsKCiAgICB2YX [...]
+<script src="data:application/x-javascript;base64,Ly8gQ3JlYXRlIGFuZCBkcmF3IHggYW5kIHkgYXhlcwpmdW5jdGlvbiBhZGRfYXhlcyhzZWxlY3Rpb24sIGRpbXMsIHNldHRpbmdzLCBzY2FsZXMpIHsKCiAgICAvLyB4IGF4aXMKICAgIHNlbGVjdGlvbi5hcHBlbmQoImciKQogICAgICAgIC5hdHRyKCJjbGFzcyIsICJ4IGF4aXMiKQogICAgICAgIC5hdHRyKCJ0cmFuc2Zvcm0iLCAidHJhbnNsYXRlKDAsIiArIGRpbXMuaGVpZ2h0ICsgIikiKQogICAgICAgIC5zdHlsZSgiZm9udC1zaXplIiwgc2V0dGluZ3MuYXhlc19mb250X3NpemUpCiAgICAgICAgLmNhbGwoc2NhbGVzLnhBeGlzKTsKCiAgICBzZWxlY3Rpb24uYXBwZW5kKCJ0ZX [...]
+<script src="data:application/x-javascript;base64,Ly8gUmV0dXJucyBkb3Qgc2l6ZSBmcm9tIGFzc29jaWF0ZWQgZGF0YQpmdW5jdGlvbiBkb3Rfc2l6ZShkYXRhLCBzZXR0aW5ncywgc2NhbGVzKSB7CiAgICB2YXIgc2l6ZSA9IHNldHRpbmdzLnBvaW50X3NpemU7CiAgICBpZiAoc2V0dGluZ3MuaGFzX3NpemVfdmFyKSB7IHNpemUgPSBzY2FsZXMuc2l6ZShkYXRhLnNpemVfdmFyKTsgfQogICAgcmV0dXJuKHNpemUpOwp9CgovLyBJbml0aWFsIGRvdCBhdHRyaWJ1dGVzCmZ1bmN0aW9uIGRvdF9pbml0IChzZWxlY3Rpb24sIHNldHRpbmdzLCBzY2FsZXMpIHsKICAgIC8vIHRvb2x0aXBzIHdoZW4gaG92ZXJpbmcgcG9pbnRzCiAgICB2YX [...]
+<script src="data:application/x-javascript;base64,CmZ1bmN0aW9uIGFkZF9hcnJvd3NfZGVmcyhzdmcsIHNldHRpbmdzLCBzY2FsZXMpIHsKICAgIC8vIDxkZWZzPgogICAgdmFyIGRlZnMgPSBzdmcuYXBwZW5kKCJkZWZzIik7CiAgICAvLyBhcnJvdyBoZWFkIG1hcmtlcnMKICAgIHNjYWxlcy5jb2xvci5yYW5nZSgpLmZvckVhY2goZnVuY3Rpb24oZCkgewogICAgICAgIGRlZnMuYXBwZW5kKCJtYXJrZXIiKQoJICAgIC5hdHRyKCJpZCIsICJhcnJvdy1oZWFkLSIgKyBzZXR0aW5ncy5odG1sX2lkICsgIi0iICsgZCkKCSAgICAuYXR0cigibWFya2VyV2lkdGgiLCAiMTAiKQoJICAgIC5hdHRyKCJtYXJrZXJIZWlnaHQiLCAiMTAiKQoJIC [...]
+<script src="data:application/x-javascript;base64,Ci8vIEluaXRpYWwgdGV4dCBsYWJlbCBhdHRyaWJ1dGVzCmZ1bmN0aW9uIGxhYmVsX2luaXQgKHNlbGVjdGlvbikgewogICAgc2VsZWN0aW9uCiAgICAgICAgLmF0dHIoInRleHQtYW5jaG9yIiwgIm1pZGRsZSIpOwp9CgovLyBDb21wdXRlIGRlZmF1bHQgdmVydGljYWwgb2Zmc2V0IGZvciBsYWJlbHMKZnVuY3Rpb24gZGVmYXVsdF9sYWJlbF9keShzaXplLCB5LCB0eXBlX3ZhcixzZXR0aW5ncykgewogICAgaWYgKHkgPCAwICYmIHR5cGVfdmFyICE9PSB1bmRlZmluZWQgJiYgdHlwZV92YXIgPT0gImFycm93IikgewogICAgICAgIHJldHVybiAoTWF0aC5zcXJ0KHNpemUpIC8gMikgKy [...]
+<script src="data:application/x-javascript;base64,Ly8gWmVybyBob3Jpem9udGFsIGFuZCB2ZXJ0aWNhbCBsaW5lcwp2YXIgZHJhd19saW5lID0gZDMubGluZSgpCiAgICAueChmdW5jdGlvbihkKSB7cmV0dXJuIGQueDt9KQogICAgLnkoZnVuY3Rpb24oZCkge3JldHVybiBkLnk7fSk7CgpmdW5jdGlvbiBsaW5lX2luaXQoc2VsZWN0aW9uKSB7CiAgICBzZWxlY3Rpb24KCS5hdHRyKCJjbGFzcyIsICJsaW5lIik7CgogICAgcmV0dXJuIHNlbGVjdGlvbjsKfQoKZnVuY3Rpb24gbGluZV9mb3JtYXR0aW5nKHNlbGVjdGlvbiwgZGltcywgc2V0dGluZ3MsIHNjYWxlcykgewogICAgc2VsZWN0aW9uCgkuYXR0cigiZCIsIGZ1bmN0aW9uKGQpIH [...]
+<script src="data:application/x-javascript;base64,Ci8vIEluaXRpYWwgZWxsaXBzZSBhdHRyaWJ1dGVzCmZ1bmN0aW9uIGVsbGlwc2VfaW5pdChzZWxlY3Rpb24pIHsKICAgIHNlbGVjdGlvbgogICAgICAgIC5zdHlsZSgiZmlsbCIsICJub25lIik7Cn0KCi8vIEFwcGx5IGZvcm1hdCB0byBlbGxpcHNlCmZ1bmN0aW9uIGVsbGlwc2VfZm9ybWF0dGluZyhzZWxlY3Rpb24sIHNldHRpbmdzLCBzY2FsZXMpIHsKCiAgICAvLyBFbGxpcHNlcyBwYXRoIGZ1bmN0aW9uCiAgICB2YXIgZWxsaXBzZUZ1bmMgPSBkMy5saW5lKCkKICAgICAgICAueChmdW5jdGlvbihkKSB7IHJldHVybiBzY2FsZXMueChkLngpOyB9KQogICAgICAgIC55KGZ1bmN0aW [...]
+<script src="data:application/x-javascript;base64,Ly8gRm9ybWF0IGxlZ2VuZCBsYWJlbApmdW5jdGlvbiBsZWdlbmRfbGFiZWxfZm9ybWF0dGluZyAoc2VsZWN0aW9uKSB7CiAgICBzZWxlY3Rpb24KICAgICAgICAuc3R5bGUoInRleHQtYW5jaG9yIiwgImJlZ2lubmluZyIpCiAgICAgICAgLnN0eWxlKCJmaWxsIiwgIiMwMDAiKQogICAgICAgIC5zdHlsZSgiZm9udC13ZWlnaHQiLCAiYm9sZCIpOwp9CgovLyBDcmVhdGUgY29sb3IgbGVnZW5kCmZ1bmN0aW9uIGFkZF9jb2xvcl9sZWdlbmQoc3ZnLCBkaW1zLCBzZXR0aW5ncywgc2NhbGVzLCBkdXJhdGlvbikgewoKICAgIC8vIERlZmF1bHQgdHJhbnNpdGlvbiBkdXJhdGlvbiB0byAwCi [...]
+<script src="data:application/x-javascript;base64,Ly8gTGFzc28gZnVuY3Rpb25zIHRvIGV4ZWN1dGUgd2hpbGUgbGFzc29pbmcKdmFyIGxhc3NvX3N0YXJ0ID0gZnVuY3Rpb24obGFzc28pIHsKICAgIGxhc3NvLml0ZW1zKCkKICAgICAgICAuZWFjaChmdW5jdGlvbihkKXsKCSAgICBpZiAoZDMuc2VsZWN0KHRoaXMpLmNsYXNzZWQoJ2RvdCcpKSB7CiAgICAgICAgICAgICAgICBkLnNjYXR0ZXJEM19sYXNzb19kb3Rfc3Ryb2tlID0gZC5zY2F0dGVyRDNfbGFzc29fZG90X3N0cm9rZSA/IGQuc2NhdHRlckQzX2xhc3NvX2RvdF9zdHJva2UgOiBkMy5zZWxlY3QodGhpcykuc3R5bGUoInN0cm9rZSIpOwogICAgICAgICAgICAgICAgZC5zY2 [...]
+<script src="data:application/x-javascript;base64,Ly8gRXhwb3J0IHRvIFNWRyBmdW5jdGlvbgpmdW5jdGlvbiBleHBvcnRfc3ZnKHNlbCwgc3ZnLCBzZXR0aW5ncykgewogICAgdmFyIHN2Z19jb250ZW50ID0gc3ZnCiAgICAgICAgLmF0dHIoInhtbG5zIiwgImh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIikKICAgICAgICAuYXR0cigidmVyc2lvbiIsIDEuMSkKICAgICAgICAubm9kZSgpLnBhcmVudE5vZGUuaW5uZXJIVE1MOwogICAgLy8gRGlydHkgZGlydHkgZGlydHkuLi4KICAgIHZhciB0bXAgPSBzdmdfY29udGVudC5yZXBsYWNlKC88ZyBjbGFzcz0iZ2Vhci1tZW51W1xzXFNdKj88XC9nPi8sICcnKTsKICAgIHZhciBzdmdfY2 [...]
+<script src="data:application/x-javascript;base64,ZnVuY3Rpb24gc2NhdHRlckQzKCkgewoKICAgIHZhciB3aWR0aCA9IDYwMCwgLy8gZGVmYXVsdCB3aWR0aAoJaGVpZ2h0ID0gNjAwLCAvLyBkZWZhdWx0IGhlaWdodAoJZGltcyA9IHt9LAoJc2V0dGluZ3MgPSB7fSwKCXNjYWxlcyA9IHt9LAoJZGF0YSA9IFtdLAoJc3ZnLAoJem9vbSwgZHJhZzsKICAgIAogICAgLy8gWm9vbSBiZWhhdmlvcgogICAgem9vbSA9IGQzLnpvb20oKQogICAgICAgIC5zY2FsZUV4dGVudChbMCwgMzJdKQogICAgICAgIC5vbigiem9vbSIsIHpvb21lZCk7CiAgICAKICAgIC8vIFpvb20gZnVuY3Rpb24KICAgIGZ1bmN0aW9uIHpvb21lZChyZXNldCkgewoJdm [...]
<style type="text/css">code{white-space: pre;}</style>
<style type="text/css">
+div.sourceCode { overflow-x: auto; }
table.sourceCode, tr.sourceCode, td.lineNumbers, td.sourceCode {
margin: 0; padding: 0; vertical-align: baseline; border: none; }
table.sourceCode { width: 100%; line-height: 100%; }
td.lineNumbers { text-align: right; padding-right: 4px; padding-left: 4px; color: #aaaaaa; border-right: 1px solid #aaaaaa; }
td.sourceCode { padding-left: 5px; }
-code > span.kw { color: #007020; font-weight: bold; }
-code > span.dt { color: #902000; }
-code > span.dv { color: #40a070; }
-code > span.bn { color: #40a070; }
-code > span.fl { color: #40a070; }
-code > span.ch { color: #4070a0; }
-code > span.st { color: #4070a0; }
-code > span.co { color: #60a0b0; font-style: italic; }
-code > span.ot { color: #007020; }
-code > span.al { color: #ff0000; font-weight: bold; }
-code > span.fu { color: #06287e; }
-code > span.er { color: #ff0000; font-weight: bold; }
+code > span.kw { color: #007020; font-weight: bold; } /* Keyword */
+code > span.dt { color: #902000; } /* DataType */
+code > span.dv { color: #40a070; } /* DecVal */
+code > span.bn { color: #40a070; } /* BaseN */
+code > span.fl { color: #40a070; } /* Float */
+code > span.ch { color: #4070a0; } /* Char */
+code > span.st { color: #4070a0; } /* String */
+code > span.co { color: #60a0b0; font-style: italic; } /* Comment */
+code > span.ot { color: #007020; } /* Other */
+code > span.al { color: #ff0000; font-weight: bold; } /* Alert */
+code > span.fu { color: #06287e; } /* Function */
+code > span.er { color: #ff0000; font-weight: bold; } /* Error */
+code > span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
+code > span.cn { color: #880000; } /* Constant */
+code > span.sc { color: #4070a0; } /* SpecialChar */
+code > span.vs { color: #4070a0; } /* VerbatimString */
+code > span.ss { color: #bb6688; } /* SpecialString */
+code > span.im { } /* Import */
+code > span.va { color: #19177c; } /* Variable */
+code > span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
+code > span.op { color: #666666; } /* Operator */
+code > span.bu { } /* BuiltIn */
+code > span.ex { } /* Extension */
+code > span.pp { color: #bc7a00; } /* Preprocessor */
+code > span.at { color: #7d9029; } /* Attribute */
+code > span.do { color: #ba2121; font-style: italic; } /* Documentation */
+code > span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
+code > span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
+code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
</style>
-<link href="data:text/css,body%20%7B%0A%20%20background%2Dcolor%3A%20%23fff%3B%0A%20%20margin%3A%201em%20auto%3B%0A%20%20max%2Dwidth%3A%20700px%3B%0A%20%20overflow%3A%20visible%3B%0A%20%20padding%2Dleft%3A%202em%3B%0A%20%20padding%2Dright%3A%202em%3B%0A%20%20font%2Dfamily%3A%20%22Open%20Sans%22%2C%20%22Helvetica%20Neue%22%2C%20Helvetica%2C%20Arial%2C%20sans%2Dserif%3B%0A%20%20font%2Dsize%3A%2014px%3B%0A%20%20line%2Dheight%3A%201%2E35%3B%0A%7D%0A%0A%23header%20%7B%0A%20%20text%2Dalign%3A% [...]
+
+<link href="data:text/css;charset=utf-8,body%20%7B%0Abackground%2Dcolor%3A%20%23fff%3B%0Amargin%3A%201em%20auto%3B%0Amax%2Dwidth%3A%20700px%3B%0Aoverflow%3A%20visible%3B%0Apadding%2Dleft%3A%202em%3B%0Apadding%2Dright%3A%202em%3B%0Afont%2Dfamily%3A%20%22Open%20Sans%22%2C%20%22Helvetica%20Neue%22%2C%20Helvetica%2C%20Arial%2C%20sans%2Dserif%3B%0Afont%2Dsize%3A%2014px%3B%0Aline%2Dheight%3A%201%2E35%3B%0A%7D%0A%23header%20%7B%0Atext%2Dalign%3A%20center%3B%0A%7D%0A%23TOC%20%7B%0Aclear%3A%20bot [...]
</head>
@@ -54,25 +100,29 @@ code > span.er { color: #ff0000; font-weight: bold; }
-<div class="fluid-row" id="header">
-
-<h1 class="title">Interactive scatterplots with scatterD3</h1>
+<h1 class="title toc-ignore">scatterD3 : a Visual Guide</h1>
<h4 class="author"><em>Julien Barnier</em></h4>
-<h4 class="date"><em>2016-03-24</em></h4>
+<h4 class="date"><em>2016-10-26</em></h4>
-</div>
<div id="TOC">
<ul>
<li><a href="#basic-scatterplot">Basic scatterplot</a></li>
+<li><a href="#categorical-x-and-y">Categorical <code>x</code> and <code>y</code></a></li>
<li><a href="#point-labels">Point labels</a></li>
-<li><a href="#mapping-colors-symbols-and-size-to-variables">Mapping colors, symbols and size to variables</a></li>
+<li><a href="#mapping-colors-symbols-size-and-opacity-to-variables">Mapping colors, symbols, size and opacity to variables</a></li>
+<li><a href="#adding-lines">Adding lines</a></li>
<li><a href="#axis-limits">Axis limits</a></li>
-<li><a href="#custom-axis-and-legend-labels">Custom axis and legend labels</a></li>
+<li><a href="#axes-and-legend-customization">Axes and legend customization</a></li>
<li><a href="#custom-tooltips">Custom tooltips</a></li>
+<li><a href="#open-urls-when-clicking-points">Open URLs when clicking points</a></li>
+<li><a href="#javascript-callback-on-clicking-point">JavaScript callback on clicking point</a></li>
+<li><a href="#javascript-zoom-callback">JavaScript zoom callback</a></li>
<li><a href="#confidence-ellipses">Confidence ellipses</a></li>
+<li><a href="#gear-menu">Gear menu</a></li>
<li><a href="#lasso-selection-tool">Lasso selection tool</a></li>
+<li><a href="#custom-labels-positions-export">Custom labels positions export</a></li>
<li><a href="#other-options">Other options</a></li>
<li><a href="#shiny-integration">Shiny integration</a><ul>
<li><a href="#transitions">Transitions</a></li>
@@ -86,107 +136,249 @@ code > span.er { color: #ff0000; font-weight: bold; }
<div id="basic-scatterplot" class="section level2">
<h2>Basic scatterplot</h2>
<p>Starting with the sample <code>mtcars</code> dataset, we can produce a basic scatterplot with the following command :</p>
-<pre class="sourceCode r"><code class="sourceCode r"><span class="kw">library</span>(scatterD3)
-<span class="kw">scatterD3</span>(<span class="dt">x =</span> mtcars$wt, <span class="dt">y =</span> mtcars$mpg)</code></pre>
-<p><div id="htmlwidget-8193" style="width:480px;height:288px;" class="scatterD3 html-widget"></div>
-<script type="application/json" data-for="htmlwidget-8193">{"x":{"data":{"x":[2.62,2.875,2.32,3.215,3.44,3.46,3.57,3.19,3.15,3.44,3.44,4.07,3.73,3.78,5.25,5.424,5.345,2.2,1.615,1.835,2.465,3.52,3.435,3.84,3.845,1.935,2.14,1.513,3.17,2.77,3.57,2.78],"y":[21,21,22.8,21.4,18.7,18.1,14.3,24.4,22.8,19.2,17.8,16.4,17.3,15.2,10.4,10.4,14.7,32.4,30.4,33.9,21.5,15.5,15.2,13.3,19.2,27.3,26,30.4,15.8,19.7,15,21.4],"point_opacity":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],"ke [...]
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">library</span>(scatterD3)
+<span class="kw">scatterD3</span>(<span class="dt">x =</span> mtcars$wt, <span class="dt">y =</span> mtcars$mpg)</code></pre></div>
+<p>You can pass data arguments as vectors, like above, but you can also give a data frame as <code>data</code> argument and then provide variable names which will be evaluated inside this data frame :</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">scatterD3</span>(<span class="dt">data =</span> mtcars , <span class="dt">x =</span> wt, <span class="dt">y =</span> mpg)</code></pre></div>
+<div id="htmlwidget-97e605cda599963ecc3c" style="width:480px;height:288px;" class="scatterD3 html-widget"></div>
+<script type="application/json" data-for="htmlwidget-97e605cda599963ecc3c">{"x":{"data":{"x":[2.62,2.875,2.32,3.215,3.44,3.46,3.57,3.19,3.15,3.44,3.44,4.07,3.73,3.78,5.25,5.424,5.345,2.2,1.615,1.835,2.465,3.52,3.435,3.84,3.845,1.935,2.14,1.513,3.17,2.77,3.57,2.78],"y":[21,21,22.8,21.4,18.7,18.1,14.3,24.4,22.8,19.2,17.8,16.4,17.3,15.2,10.4,10.4,14.7,32.4,30.4,33.9,21.5,15.5,15.2,13.3,19.2,27.3,26,30.4,15.8,19.7,15,21.4],"key_var":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,2 [...]
<p>This will display a simple visualization with the given variables as <code>x</code> and <code>y</code> axis. There are several interactive features directly available :</p>
<ul>
<li>you can zoom in and out with the mouse wheel while the mouse cursor is on the plot</li>
<li>you can pan the plot by dragging with your mouse</li>
<li>by hovering over a point, you can display a small tooltip window giving the <code>x</code> and <code>y</code> values</li>
</ul>
-<p>You can customize the points size with the <code>point_size</code> parameter, their opacity with <code>point_opacity</code>, and you can force the plot to have a 1:1 fixed aspect ratio with <code>fixed = TRUE</code>.</p>
-<pre class="sourceCode r"><code class="sourceCode r"><span class="kw">scatterD3</span>(<span class="dt">x =</span> mtcars$wt, <span class="dt">y =</span> mtcars$mpg, <span class="dt">point_size =</span> <span class="dv">15</span>, <span class="dt">point_opacity =</span> <span class="fl">0.5</span>, <span class="dt">fixed =</span> <span class="ot">TRUE</span>)</code></pre>
-<p><div id="htmlwidget-9853" style="width:480px;height:288px;" class="scatterD3 html-widget"></div>
-<script type="application/json" data-for="htmlwidget-9853">{"x":{"data":{"x":[2.62,2.875,2.32,3.215,3.44,3.46,3.57,3.19,3.15,3.44,3.44,4.07,3.73,3.78,5.25,5.424,5.345,2.2,1.615,1.835,2.465,3.52,3.435,3.84,3.845,1.935,2.14,1.513,3.17,2.77,3.57,2.78],"y":[21,21,22.8,21.4,18.7,18.1,14.3,24.4,22.8,19.2,17.8,16.4,17.3,15.2,10.4,10.4,14.7,32.4,30.4,33.9,21.5,15.5,15.2,13.3,19.2,27.3,26,30.4,15.8,19.7,15,21.4],"point_opacity":[0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5, [...]
+<p>You can customize the points size with the <code>point_size</code> parameter, their global opacity with <code>point_opacity</code>, and you can force the plot to have a 1:1 fixed aspect ratio with <code>fixed = TRUE</code>. You can also manually specify the points color with the <code>colors</code> argument</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">scatterD3</span>(<span class="dt">data =</span> mtcars, <span class="dt">x =</span> wt, <span class="dt">y =</span> mpg,
+ <span class="dt">point_size =</span> <span class="dv">35</span>, <span class="dt">point_opacity =</span> <span class="fl">0.5</span>, <span class="dt">fixed =</span> <span class="ot">TRUE</span>,
+ <span class="dt">colors =</span> <span class="st">"#A94175"</span>)</code></pre></div>
+<div id="htmlwidget-9fed3d6c3c41a8f07967" style="width:480px;height:288px;" class="scatterD3 html-widget"></div>
+<script type="application/json" data-for="htmlwidget-9fed3d6c3c41a8f07967">{"x":{"data":{"x":[2.62,2.875,2.32,3.215,3.44,3.46,3.57,3.19,3.15,3.44,3.44,4.07,3.73,3.78,5.25,5.424,5.345,2.2,1.615,1.835,2.465,3.52,3.435,3.84,3.845,1.935,2.14,1.513,3.17,2.77,3.57,2.78],"y":[21,21,22.8,21.4,18.7,18.1,14.3,24.4,22.8,19.2,17.8,16.4,17.3,15.2,10.4,10.4,14.7,32.4,30.4,33.9,21.5,15.5,15.2,13.3,19.2,27.3,26,30.4,15.8,19.7,15,21.4],"key_var":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,2 [...]
+<p>You can change size and opacity of points when hovering with the <code>hover_size</code> and <code>hover_opacity</code> settings :</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">scatterD3</span>(<span class="dt">data =</span> mtcars, <span class="dt">x =</span> wt, <span class="dt">y =</span> mpg,
+ <span class="dt">point_size =</span> <span class="dv">100</span>, <span class="dt">point_opacity =</span> <span class="fl">0.5</span>,
+ <span class="dt">hover_size =</span> <span class="dv">4</span>, <span class="dt">hover_opacity =</span> <span class="dv">1</span>)</code></pre></div>
+<div id="htmlwidget-0dbd9c3b49d8a00b2be6" style="width:480px;height:288px;" class="scatterD3 html-widget"></div>
+<script type="application/json" data-for="htmlwidget-0dbd9c3b49d8a00b2be6">{"x":{"data":{"x":[2.62,2.875,2.32,3.215,3.44,3.46,3.57,3.19,3.15,3.44,3.44,4.07,3.73,3.78,5.25,5.424,5.345,2.2,1.615,1.835,2.465,3.52,3.435,3.84,3.845,1.935,2.14,1.513,3.17,2.77,3.57,2.78],"y":[21,21,22.8,21.4,18.7,18.1,14.3,24.4,22.8,19.2,17.8,16.4,17.3,15.2,10.4,10.4,14.7,32.4,30.4,33.9,21.5,15.5,15.2,13.3,19.2,27.3,26,30.4,15.8,19.7,15,21.4],"key_var":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,2 [...]
+</div>
+<div id="categorical-x-and-y" class="section level2">
+<h2>Categorical <code>x</code> and <code>y</code></h2>
+<p>If the <code>x</code> or <code>y</code> variable is not numeric or is a factor, then an ordinal scale is used for the corresponding axis. Note that zooming is then not possible along this axis.</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">mtcars$cyl_fac <-<span class="st"> </span><span class="kw">paste</span>(mtcars$cyl, <span class="st">"cylinders"</span>)
+<span class="kw">scatterD3</span>(<span class="dt">data =</span> mtcars, <span class="dt">x =</span> cyl_fac, <span class="dt">y =</span> mpg)</code></pre></div>
+<div id="htmlwidget-07842fabc3cfc03a40a5" style="width:480px;height:288px;" class="scatterD3 html-widget"></div>
+<script type="application/json" data-for="htmlwidget-07842fabc3cfc03a40a5">{"x":{"data":{"x":["6 cylinders","6 cylinders","4 cylinders","6 cylinders","8 cylinders","6 cylinders","8 cylinders","4 cylinders","4 cylinders","6 cylinders","6 cylinders","8 cylinders","8 cylinders","8 cylinders","8 cylinders","8 cylinders","8 cylinders","4 cylinders","4 cylinders","4 cylinders","4 cylinders","8 cylinders","8 cylinders","8 cylinders","8 cylinders","4 cylinders","4 cylinders","4 cylinders","8 cyl [...]
+<p>You can use the <code>left_margin</code> argument when using a categorical <code>y</code> variable if the axis labels are not entirely visible :</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">scatterD3</span>(<span class="dt">data =</span> mtcars, <span class="dt">x =</span> wt, <span class="dt">y =</span> cyl_fac, <span class="dt">left_margin =</span> <span class="dv">80</span>)</code></pre></div>
+<div id="htmlwidget-8b19f92c4992b727c4c6" style="width:480px;height:288px;" class="scatterD3 html-widget"></div>
+<script type="application/json" data-for="htmlwidget-8b19f92c4992b727c4c6">{"x":{"data":{"x":[2.62,2.875,2.32,3.215,3.44,3.46,3.57,3.19,3.15,3.44,3.44,4.07,3.73,3.78,5.25,5.424,5.345,2.2,1.615,1.835,2.465,3.52,3.435,3.84,3.845,1.935,2.14,1.513,3.17,2.77,3.57,2.78],"y":["6 cylinders","6 cylinders","4 cylinders","6 cylinders","8 cylinders","6 cylinders","8 cylinders","4 cylinders","4 cylinders","6 cylinders","6 cylinders","8 cylinders","8 cylinders","8 cylinders","8 cylinders","8 cylinders [...]
</div>
<div id="point-labels" class="section level2">
<h2>Point labels</h2>
<p>You can add text labels to the points by passing a character vector to the <code>lab</code> parameter. Labels size are controlled by the <code>labels_size</code> parameter.</p>
-<pre class="sourceCode r"><code class="sourceCode r"><span class="kw">scatterD3</span>(<span class="dt">x =</span> mtcars$wt, <span class="dt">y =</span> mtcars$mpg, <span class="dt">lab =</span> <span class="kw">rownames</span>(mtcars), <span class="dt">labels_size =</span> <span class="dv">9</span>)</code></pre>
-<p><div id="htmlwidget-5294" style="width:480px;height:288px;" class="scatterD3 html-widget"></div>
-<script type="application/json" data-for="htmlwidget-5294">{"x":{"data":{"x":[2.62,2.875,2.32,3.215,3.44,3.46,3.57,3.19,3.15,3.44,3.44,4.07,3.73,3.78,5.25,5.424,5.345,2.2,1.615,1.835,2.465,3.52,3.435,3.84,3.845,1.935,2.14,1.513,3.17,2.77,3.57,2.78],"y":[21,21,22.8,21.4,18.7,18.1,14.3,24.4,22.8,19.2,17.8,16.4,17.3,15.2,10.4,10.4,14.7,32.4,30.4,33.9,21.5,15.5,15.2,13.3,19.2,27.3,26,30.4,15.8,19.7,15,21.4],"lab":["Mazda RX4","Mazda RX4 Wag","Datsun 710","Hornet 4 Drive","Hornet Sportabout", [...]
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">mtcars$names <-<span class="st"> </span><span class="kw">rownames</span>(mtcars)
+<span class="kw">scatterD3</span>(<span class="dt">data =</span> mtcars, <span class="dt">x =</span> wt, <span class="dt">y =</span> mpg, <span class="dt">lab =</span> names, <span class="dt">labels_size =</span> <span class="dv">9</span>)</code></pre></div>
+<div id="htmlwidget-e82e27f5e0990187761d" style="width:480px;height:288px;" class="scatterD3 html-widget"></div>
+<script type="application/json" data-for="htmlwidget-e82e27f5e0990187761d">{"x":{"data":{"x":[2.62,2.875,2.32,3.215,3.44,3.46,3.57,3.19,3.15,3.44,3.44,4.07,3.73,3.78,5.25,5.424,5.345,2.2,1.615,1.835,2.465,3.52,3.435,3.84,3.845,1.935,2.14,1.513,3.17,2.77,3.57,2.78],"y":[21,21,22.8,21.4,18.7,18.1,14.3,24.4,22.8,19.2,17.8,16.4,17.3,15.2,10.4,10.4,14.7,32.4,30.4,33.9,21.5,15.5,15.2,13.3,19.2,27.3,26,30.4,15.8,19.7,15,21.4],"lab":["Mazda RX4","Mazda RX4 Wag","Datsun 710","Hornet 4 Drive","Hor [...]
<p>Note that text labels are fully movable : click and drag a label with your mouse to place it where you want. Custom positions are preserved while zooming/panning.</p>
</div>
-<div id="mapping-colors-symbols-and-size-to-variables" class="section level2">
-<h2>Mapping colors, symbols and size to variables</h2>
+<div id="mapping-colors-symbols-size-and-opacity-to-variables" class="section level2">
+<h2>Mapping colors, symbols, size and opacity to variables</h2>
<p>By passing vectors to the <code>col_var</code> and/or <code>symbol_var</code> arguments, you can map points colors and symbols to other variables.</p>
-<pre class="sourceCode r"><code class="sourceCode r"><span class="kw">scatterD3</span>(<span class="dt">x =</span> mtcars$wt, <span class="dt">y =</span> mtcars$mpg, <span class="dt">col_var =</span> mtcars$cyl, <span class="dt">symbol_var =</span> mtcars$gear)</code></pre>
-<p><div id="htmlwidget-8420" style="width:480px;height:288px;" class="scatterD3 html-widget"></div>
-<script type="application/json" data-for="htmlwidget-8420">{"x":{"data":{"x":[2.62,2.875,2.32,3.215,3.44,3.46,3.57,3.19,3.15,3.44,3.44,4.07,3.73,3.78,5.25,5.424,5.345,2.2,1.615,1.835,2.465,3.52,3.435,3.84,3.845,1.935,2.14,1.513,3.17,2.77,3.57,2.78],"y":[21,21,22.8,21.4,18.7,18.1,14.3,24.4,22.8,19.2,17.8,16.4,17.3,15.2,10.4,10.4,14.7,32.4,30.4,33.9,21.5,15.5,15.2,13.3,19.2,27.3,26,30.4,15.8,19.7,15,21.4],"point_opacity":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],"co [...]
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">scatterD3</span>(<span class="dt">data =</span> mtcars, <span class="dt">x =</span> wt, <span class="dt">y =</span> mpg, <span class="dt">col_var =</span> cyl, <span class="dt">symbol_var =</span> gear)</code></pre></div>
+<div id="htmlwidget-af7e736b76ec6afa06a7" style="width:480px;height:288px;" class="scatterD3 html-widget"></div>
+<script type="application/json" data-for="htmlwidget-af7e736b76ec6afa06a7">{"x":{"data":{"x":[2.62,2.875,2.32,3.215,3.44,3.46,3.57,3.19,3.15,3.44,3.44,4.07,3.73,3.78,5.25,5.424,5.345,2.2,1.615,1.835,2.465,3.52,3.435,3.84,3.845,1.935,2.14,1.513,3.17,2.77,3.57,2.78],"y":[21,21,22.8,21.4,18.7,18.1,14.3,24.4,22.8,19.2,17.8,16.4,17.3,15.2,10.4,10.4,14.7,32.4,30.4,33.9,21.5,15.5,15.2,13.3,19.2,27.3,26,30.4,15.8,19.7,15,21.4],"col_var":["6","6","4","6","8","6","8","4","4","6","6","8","8","8","8 [...]
<p>A legend is then automatically added. You can manually specify its width with the <code>legend_width</code> argument. Use <code>legend_width = 0</code> to disable it entirely.</p>
<p>Note that when hovering over a legend item with your mouse, the corresponding points are highlighted. Also note that the mapped variables values are automatically added to the default tooltips.</p>
<p>You can also map symbol sizes with a variable with the <code>size_var</code> argument. <code>size_range</code> allows to customize the sizes range :</p>
-<pre class="sourceCode r"><code class="sourceCode r"><span class="kw">scatterD3</span>(<span class="dt">x =</span> mtcars$wt, <span class="dt">y =</span> mtcars$mpg, <span class="dt">col_var =</span> mtcars$cyl, <span class="dt">size_var =</span> mtcars$hp,
- <span class="dt">size_range =</span> <span class="kw">c</span>(<span class="dv">10</span>,<span class="dv">1000</span>), <span class="dt">point_opacity =</span> <span class="fl">0.7</span>)</code></pre>
-<pre><code>## Warning in scatterD3(x = mtcars$wt, y = mtcars$mpg, col_var = mtcars$cyl, :
-## NA values in size_var. Values set to min(0, size_var)</code></pre>
-<p><div id="htmlwidget-3206" style="width:480px;height:288px;" class="scatterD3 html-widget"></div>
-<script type="application/json" data-for="htmlwidget-3206">{"x":{"data":{"x":[2.62,2.875,2.32,3.215,3.44,3.46,3.57,3.19,3.15,3.44,3.44,4.07,3.73,3.78,5.25,5.424,5.345,2.2,1.615,1.835,2.465,3.52,3.435,3.84,3.845,1.935,2.14,1.513,3.17,2.77,3.57,2.78],"y":[21,21,22.8,21.4,18.7,18.1,14.3,24.4,22.8,19.2,17.8,16.4,17.3,15.2,10.4,10.4,14.7,32.4,30.4,33.9,21.5,15.5,15.2,13.3,19.2,27.3,26,30.4,15.8,19.7,15,21.4],"point_opacity":[0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.7,0.7, [...]
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">scatterD3</span>(<span class="dt">data =</span> mtcars, <span class="dt">x =</span> wt, <span class="dt">y =</span> mpg, <span class="dt">col_var =</span> cyl, <span class="dt">size_var =</span> hp,
+ <span class="dt">size_range =</span> <span class="kw">c</span>(<span class="dv">10</span>,<span class="dv">1000</span>), <span class="dt">point_opacity =</span> <span class="fl">0.7</span>)</code></pre></div>
+<div id="htmlwidget-f4953e626a1dec16fe8a" style="width:480px;height:288px;" class="scatterD3 html-widget"></div>
+<script type="application/json" data-for="htmlwidget-f4953e626a1dec16fe8a">{"x":{"data":{"x":[2.62,2.875,2.32,3.215,3.44,3.46,3.57,3.19,3.15,3.44,3.44,4.07,3.73,3.78,5.25,5.424,5.345,2.2,1.615,1.835,2.465,3.52,3.435,3.84,3.845,1.935,2.14,1.513,3.17,2.77,3.57,2.78],"y":[21,21,22.8,21.4,18.7,18.1,14.3,24.4,22.8,19.2,17.8,16.4,17.3,15.2,10.4,10.4,14.7,32.4,30.4,33.9,21.5,15.5,15.2,13.3,19.2,27.3,26,30.4,15.8,19.7,15,21.4],"col_var":["6","6","4","6","8","6","8","4","4","6","6","8","8","8","8 [...]
+<p>You can specify custom colors by passing a vector of hexadecimal strings to the <code>colors</code> argument. If the vector is named, then the colors will be associated with their names within <code>col_var</code>.</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">scatterD3</span>(<span class="dt">data =</span> mtcars, <span class="dt">x =</span> wt, <span class="dt">y =</span> mpg, <span class="dt">col_var =</span> cyl,
+ <span class="dt">colors =</span> <span class="kw">c</span>(<span class="st">"4"</span> =<span class="st"> "#ECD078"</span>, <span class="st">"8"</span> =<span class="st"> "#C02942"</span>, <span class="st">"6"</span> =<span class="st"> "#53777A"</span>))</code></pre></div>
+<div id="htmlwidget-ea7d3194480fe1183a2d" style="width:480px;height:288px;" class="scatterD3 html-widget"></div>
+<script type="application/json" data-for="htmlwidget-ea7d3194480fe1183a2d">{"x":{"data":{"x":[2.62,2.875,2.32,3.215,3.44,3.46,3.57,3.19,3.15,3.44,3.44,4.07,3.73,3.78,5.25,5.424,5.345,2.2,1.615,1.835,2.465,3.52,3.435,3.84,3.845,1.935,2.14,1.513,3.17,2.77,3.57,2.78],"y":[21,21,22.8,21.4,18.7,18.1,14.3,24.4,22.8,19.2,17.8,16.4,17.3,15.2,10.4,10.4,14.7,32.4,30.4,33.9,21.5,15.5,15.2,13.3,19.2,27.3,26,30.4,15.8,19.7,15,21.4],"col_var":["6","6","4","6","8","6","8","4","4","6","6","8","8","8","8 [...]
+<p>If <code>col_var</code> is numeric, not a factor, and has more than 6 unique values, it is considered as continuous, and drawn accordingly using the Veridis d3 interpolator.</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">scatterD3</span>(<span class="dt">data =</span> mtcars, <span class="dt">x =</span> wt, <span class="dt">y =</span> mpg, <span class="dt">col_var =</span> disp)</code></pre></div>
+<div id="htmlwidget-eee35bf4322787164410" style="width:480px;height:288px;" class="scatterD3 html-widget"></div>
+<script type="application/json" data-for="htmlwidget-eee35bf4322787164410">{"x":{"data":{"x":[2.62,2.875,2.32,3.215,3.44,3.46,3.57,3.19,3.15,3.44,3.44,4.07,3.73,3.78,5.25,5.424,5.345,2.2,1.615,1.835,2.465,3.52,3.435,3.84,3.845,1.935,2.14,1.513,3.17,2.77,3.57,2.78],"y":[21,21,22.8,21.4,18.7,18.1,14.3,24.4,22.8,19.2,17.8,16.4,17.3,15.2,10.4,10.4,14.7,32.4,30.4,33.9,21.5,15.5,15.2,13.3,19.2,27.3,26,30.4,15.8,19.7,15,21.4],"col_var":[160,160,108,258,360,225,360,146.7,140.8,167.6,167.6,275.8, [...]
+<p>In this case, any <code>colors</code> argument is ignored. You can force <code>col_var</code> to be considered as continuous with <code>col_continuous = TRUE</code>.</p>
+<p>You can also use the <code>opacity_var</code> argument to map point opacity to a variable. Note that for now no legend for opacity is added, though.</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">scatterD3</span>(<span class="dt">data=</span>mtcars, <span class="dt">x=</span>mpg, <span class="dt">y=</span>wt, <span class="dt">opacity_var =</span> drat)</code></pre></div>
+<div id="htmlwidget-9d3dd0d3f032d7d2a100" style="width:480px;height:288px;" class="scatterD3 html-widget"></div>
+<script type="application/json" data-for="htmlwidget-9d3dd0d3f032d7d2a100">{"x":{"data":{"x":[21,21,22.8,21.4,18.7,18.1,14.3,24.4,22.8,19.2,17.8,16.4,17.3,15.2,10.4,10.4,14.7,32.4,30.4,33.9,21.5,15.5,15.2,13.3,19.2,27.3,26,30.4,15.8,19.7,15,21.4],"y":[2.62,2.875,2.32,3.215,3.44,3.46,3.57,3.19,3.15,3.44,3.44,4.07,3.73,3.78,5.25,5.424,5.345,2.2,1.615,1.835,2.465,3.52,3.435,3.84,3.845,1.935,2.14,1.513,3.17,2.77,3.57,2.78],"opacity_var":[3.9,3.9,3.85,3.08,3.15,2.76,3.21,3.69,3.92,3.92,3.92,3 [...]
+</div>
+<div id="adding-lines" class="section level2">
+<h2>Adding lines</h2>
+<p>In addition to your data points, you can add to your scatterplot. This is done vy passing a <em>data frame</em> to the <code>lines</code> argument. This <em>data frame</em> must have at least two columns called <code>slope</code> and <code>intercept</code>, and as many rows as lines you want to draw.</p>
+<p>For example, if you want to add a 1:1 line :</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">scatterD3</span>(<span class="dt">data =</span> mtcars, <span class="dt">x =</span> wt, <span class="dt">y =</span> mpg, <span class="dt">fixed =</span> <span class="ot">TRUE</span>,
+ <span class="dt">lines =</span> <span class="kw">data.frame</span>(<span class="dt">slope =</span> <span class="dv">1</span>, <span class="dt">intercept =</span> <span class="dv">0</span>))</code></pre></div>
+<div id="htmlwidget-071de03c0b0b4f68b4f4" style="width:480px;height:288px;" class="scatterD3 html-widget"></div>
+<script type="application/json" data-for="htmlwidget-071de03c0b0b4f68b4f4">{"x":{"data":{"x":[2.62,2.875,2.32,3.215,3.44,3.46,3.57,3.19,3.15,3.44,3.44,4.07,3.73,3.78,5.25,5.424,5.345,2.2,1.615,1.835,2.465,3.52,3.435,3.84,3.845,1.935,2.14,1.513,3.17,2.77,3.57,2.78],"y":[21,21,22.8,21.4,18.7,18.1,14.3,24.4,22.8,19.2,17.8,16.4,17.3,15.2,10.4,10.4,14.7,32.4,30.4,33.9,21.5,15.5,15.2,13.3,19.2,27.3,26,30.4,15.8,19.7,15,21.4],"key_var":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,2 [...]
+<p>You can style your lines by adding <code>stroke</code>, <code>stroke_width</code> and <code>stroke_dasharray</code> columns. These columns values will be added as <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Fills_and_Strokes">corresponding styles</a> to the generated SVG line. So if you want a wide dashed red horizontal line :</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">scatterD3</span>(<span class="dt">data =</span> mtcars, <span class="dt">x =</span> wt, <span class="dt">y =</span> mpg,
+ <span class="dt">lines =</span> <span class="kw">data.frame</span>(<span class="dt">slope =</span> <span class="dv">0</span>,
+ <span class="dt">intercept =</span> <span class="dv">30</span>,
+ <span class="dt">stroke =</span> <span class="st">"red"</span>,
+ <span class="dt">stroke_width =</span> <span class="dv">5</span>,
+ <span class="dt">stroke_dasharray =</span> <span class="st">"10,5"</span>))</code></pre></div>
+<div id="htmlwidget-bc358db28833790c4aee" style="width:480px;height:288px;" class="scatterD3 html-widget"></div>
+<script type="application/json" data-for="htmlwidget-bc358db28833790c4aee">{"x":{"data":{"x":[2.62,2.875,2.32,3.215,3.44,3.46,3.57,3.19,3.15,3.44,3.44,4.07,3.73,3.78,5.25,5.424,5.345,2.2,1.615,1.835,2.465,3.52,3.435,3.84,3.845,1.935,2.14,1.513,3.17,2.77,3.57,2.78],"y":[21,21,22.8,21.4,18.7,18.1,14.3,24.4,22.8,19.2,17.8,16.4,17.3,15.2,10.4,10.4,14.7,32.4,30.4,33.9,21.5,15.5,15.2,13.3,19.2,27.3,26,30.4,15.8,19.7,15,21.4],"key_var":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,2 [...]
+<p>If you want to draw a vertical line, pass the <code>Inf</code> value to <code>slope</code>. The value of <code>intercept</code> is then interpreted as the intercept along the x axis.</p>
+<p>By default, if no <code>lines</code> argument is provided two dashed horizontal and vertical lines are drawn through the origin, which is equivalent to :</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">scatterD3</span>(<span class="dt">data =</span> mtcars, <span class="dt">x =</span> wt, <span class="dt">y =</span> mpg, <span class="dt">fixed =</span> <span class="ot">TRUE</span>,
+ <span class="dt">lines =</span> <span class="kw">data.frame</span>(<span class="dt">slope =</span> <span class="kw">c</span>(<span class="dv">0</span>, <span class="ot">Inf</span>),
+ <span class="dt">intercept =</span> <span class="kw">c</span>(<span class="dv">0</span>, <span class="dv">0</span>),
+ <span class="dt">stroke =</span> <span class="st">"#000"</span>,
+ <span class="dt">stroke_width =</span> <span class="dv">1</span>,
+ <span class="dt">stroke_dasharray =</span> <span class="dv">5</span>))</code></pre></div>
+<div id="htmlwidget-70706f9e21dff2932d03" style="width:480px;height:288px;" class="scatterD3 html-widget"></div>
+<script type="application/json" data-for="htmlwidget-70706f9e21dff2932d03">{"x":{"data":{"x":[2.62,2.875,2.32,3.215,3.44,3.46,3.57,3.19,3.15,3.44,3.44,4.07,3.73,3.78,5.25,5.424,5.345,2.2,1.615,1.835,2.465,3.52,3.435,3.84,3.845,1.935,2.14,1.513,3.17,2.77,3.57,2.78],"y":[21,21,22.8,21.4,18.7,18.1,14.3,24.4,22.8,19.2,17.8,16.4,17.3,15.2,10.4,10.4,14.7,32.4,30.4,33.9,21.5,15.5,15.2,13.3,19.2,27.3,26,30.4,15.8,19.7,15,21.4],"key_var":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,2 [...]
</div>
<div id="axis-limits" class="section level2">
<h2>Axis limits</h2>
<p>You can manually specify the <code>x</code> or <code>y</code> axis limits with the <code>xlim</code> and <code>ylim</code> arguments :</p>
-<pre class="sourceCode r"><code class="sourceCode r"><span class="kw">scatterD3</span>(<span class="dt">x =</span> mtcars$wt, <span class="dt">y =</span> mtcars$mpg, <span class="dt">xlim=</span><span class="kw">c</span>(<span class="dv">0</span>,<span class="dv">10</span>), <span class="dt">ylim=</span><span class="kw">c</span>(<span class="dv">10</span>,<span class="dv">35</span>))</code></pre>
-<p><div id="htmlwidget-1869" style="width:480px;height:288px;" class="scatterD3 html-widget"></div>
-<script type="application/json" data-for="htmlwidget-1869">{"x":{"data":{"x":[2.62,2.875,2.32,3.215,3.44,3.46,3.57,3.19,3.15,3.44,3.44,4.07,3.73,3.78,5.25,5.424,5.345,2.2,1.615,1.835,2.465,3.52,3.435,3.84,3.845,1.935,2.14,1.513,3.17,2.77,3.57,2.78],"y":[21,21,22.8,21.4,18.7,18.1,14.3,24.4,22.8,19.2,17.8,16.4,17.3,15.2,10.4,10.4,14.7,32.4,30.4,33.9,21.5,15.5,15.2,13.3,19.2,27.3,26,30.4,15.8,19.7,15,21.4],"point_opacity":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],"ke [...]
-</div>
-<div id="custom-axis-and-legend-labels" class="section level2">
-<h2>Custom axis and legend labels</h2>
-<p>You can customize the axis and legend labels with <code>xlab</code>, <code>ylab</code>, <code>col_lab</code>, <code>symbol_lab</code> and <code>size_lab</code> :</p>
-<pre class="sourceCode r"><code class="sourceCode r"><span class="kw">scatterD3</span>(<span class="dt">x =</span> mtcars$wt, <span class="dt">y =</span> mtcars$mpg, <span class="dt">col_var =</span> mtcars$cyl, <span class="dt">symbol_var =</span> mtcars$gear,
- <span class="dt">xlab =</span> <span class="st">"Weight"</span>, <span class="dt">ylab =</span> <span class="st">"Mpg"</span>, <span class="dt">col_lab =</span> <span class="st">"Cylinders"</span>, <span class="dt">symbol_lab =</span> <span class="st">"Gears"</span>)</code></pre>
-<p><div id="htmlwidget-1610" style="width:480px;height:288px;" class="scatterD3 html-widget"></div>
-<script type="application/json" data-for="htmlwidget-1610">{"x":{"data":{"x":[2.62,2.875,2.32,3.215,3.44,3.46,3.57,3.19,3.15,3.44,3.44,4.07,3.73,3.78,5.25,5.424,5.345,2.2,1.615,1.835,2.465,3.52,3.435,3.84,3.845,1.935,2.14,1.513,3.17,2.77,3.57,2.78],"y":[21,21,22.8,21.4,18.7,18.1,14.3,24.4,22.8,19.2,17.8,16.4,17.3,15.2,10.4,10.4,14.7,32.4,30.4,33.9,21.5,15.5,15.2,13.3,19.2,27.3,26,30.4,15.8,19.7,15,21.4],"point_opacity":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],"co [...]
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">scatterD3</span>(<span class="dt">data =</span> mtcars, <span class="dt">x =</span> wt, <span class="dt">y =</span> mpg, <span class="dt">xlim=</span><span class="kw">c</span>(<span class="dv">0</span>,<span class="dv">10</span>), <span class="dt">ylim=</span><span class="kw">c</span>(<span class="dv">10</span>,<span class="dv">35</span>))</code></pre></div>
+<div id="htmlwidget-3af7722fc7d6ae36799c" style="width:480px;height:288px;" class="scatterD3 html-widget"></div>
+<script type="application/json" data-for="htmlwidget-3af7722fc7d6ae36799c">{"x":{"data":{"x":[2.62,2.875,2.32,3.215,3.44,3.46,3.57,3.19,3.15,3.44,3.44,4.07,3.73,3.78,5.25,5.424,5.345,2.2,1.615,1.835,2.465,3.52,3.435,3.84,3.845,1.935,2.14,1.513,3.17,2.77,3.57,2.78],"y":[21,21,22.8,21.4,18.7,18.1,14.3,24.4,22.8,19.2,17.8,16.4,17.3,15.2,10.4,10.4,14.7,32.4,30.4,33.9,21.5,15.5,15.2,13.3,19.2,27.3,26,30.4,15.8,19.7,15,21.4],"key_var":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,2 [...]
+</div>
+<div id="axes-and-legend-customization" class="section level2">
+<h2>Axes and legend customization</h2>
+<p>You can customize the value of the axes and legend labels with <code>xlab</code>, <code>ylab</code>, <code>col_lab</code>, <code>symbol_lab</code> and <code>size_lab</code> :</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">scatterD3</span>(<span class="dt">data =</span> mtcars, <span class="dt">x =</span> wt, <span class="dt">y =</span> mpg, <span class="dt">col_var =</span> cyl, <span class="dt">symbol_var =</span> gear,
+ <span class="dt">xlab =</span> <span class="st">"Weight"</span>, <span class="dt">ylab =</span> <span class="st">"Mpg"</span>, <span class="dt">col_lab =</span> <span class="st">"Cylinders"</span>, <span class="dt">symbol_lab =</span> <span class="st">"Gears"</span>)</code></pre></div>
+<div id="htmlwidget-d2a2c19ef89638e79bfc" style="width:480px;height:288px;" class="scatterD3 html-widget"></div>
+<script type="application/json" data-for="htmlwidget-d2a2c19ef89638e79bfc">{"x":{"data":{"x":[2.62,2.875,2.32,3.215,3.44,3.46,3.57,3.19,3.15,3.44,3.44,4.07,3.73,3.78,5.25,5.424,5.345,2.2,1.615,1.835,2.465,3.52,3.435,3.84,3.845,1.935,2.14,1.513,3.17,2.77,3.57,2.78],"y":[21,21,22.8,21.4,18.7,18.1,14.3,24.4,22.8,19.2,17.8,16.4,17.3,15.2,10.4,10.4,14.7,32.4,30.4,33.9,21.5,15.5,15.2,13.3,19.2,27.3,26,30.4,15.8,19.7,15,21.4],"col_var":["6","6","4","6","8","6","8","4","4","6","6","8","8","8","8 [...]
<p>Note that default tooltips are updated accordingly.</p>
+<p>You can also change the font size of axes and legend text with <code>axes_font_size</code> and <code>legend_font_size</code> :</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">scatterD3</span>(<span class="dt">data =</span> mtcars, <span class="dt">x =</span> wt, <span class="dt">y =</span> mpg, <span class="dt">col_var =</span> cyl,
+ <span class="dt">xlab =</span> <span class="st">"Weight"</span>, <span class="dt">ylab =</span> <span class="st">"Mpg"</span>,
+ <span class="dt">axes_font_size =</span> <span class="st">"120%"</span>,
+ <span class="dt">legend_font_size =</span> <span class="st">"14px"</span>)</code></pre></div>
+<div id="htmlwidget-c1f21846636e07e6fdb6" style="width:480px;height:288px;" class="scatterD3 html-widget"></div>
+<script type="application/json" data-for="htmlwidget-c1f21846636e07e6fdb6">{"x":{"data":{"x":[2.62,2.875,2.32,3.215,3.44,3.46,3.57,3.19,3.15,3.44,3.44,4.07,3.73,3.78,5.25,5.424,5.345,2.2,1.615,1.835,2.465,3.52,3.435,3.84,3.845,1.935,2.14,1.513,3.17,2.77,3.57,2.78],"y":[21,21,22.8,21.4,18.7,18.1,14.3,24.4,22.8,19.2,17.8,16.4,17.3,15.2,10.4,10.4,14.7,32.4,30.4,33.9,21.5,15.5,15.2,13.3,19.2,27.3,26,30.4,15.8,19.7,15,21.4],"col_var":["6","6","4","6","8","6","8","4","4","6","6","8","8","8","8 [...]
+<p>You can provide any CSS compatible value, wether a fixed size such as <code>2em</code> or a relative one like <code>95%</code>.</p>
</div>
<div id="custom-tooltips" class="section level2">
<h2>Custom tooltips</h2>
<p>If the default tooltips don’t suit your needs, you can customize them by providing a character vector to the <code>tooltip_text</code> argument. This can contain HTML tags for formatting.</p>
-<pre class="sourceCode r"><code class="sourceCode r">tooltips <-<span class="st"> </span><span class="kw">paste</span>(<span class="st">"This is an incredible <strong>"</span>, <span class="kw">rownames</span>(mtcars),<span class="st">"</strong><br />with "</span>,
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">tooltips <-<span class="st"> </span><span class="kw">paste</span>(<span class="st">"This is an incredible <strong>"</span>, <span class="kw">rownames</span>(mtcars),<span class="st">"</strong><br />with "</span>,
mtcars$cyl, <span class="st">"cylinders !"</span>)
-<span class="kw">scatterD3</span>(<span class="dt">x =</span> mtcars$wt, <span class="dt">y =</span> mtcars$mpg, <span class="dt">tooltip_text =</span> tooltips)</code></pre>
-<p><div id="htmlwidget-1143" style="width:480px;height:288px;" class="scatterD3 html-widget"></div>
-<script type="application/json" data-for="htmlwidget-1143">{"x":{"data":{"x":[2.62,2.875,2.32,3.215,3.44,3.46,3.57,3.19,3.15,3.44,3.44,4.07,3.73,3.78,5.25,5.424,5.345,2.2,1.615,1.835,2.465,3.52,3.435,3.84,3.845,1.935,2.14,1.513,3.17,2.77,3.57,2.78],"y":[21,21,22.8,21.4,18.7,18.1,14.3,24.4,22.8,19.2,17.8,16.4,17.3,15.2,10.4,10.4,14.7,32.4,30.4,33.9,21.5,15.5,15.2,13.3,19.2,27.3,26,30.4,15.8,19.7,15,21.4],"point_opacity":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],"ke [...]
+<span class="kw">scatterD3</span>(<span class="dt">data =</span> mtcars, <span class="dt">x =</span> wt, <span class="dt">y =</span> mpg, <span class="dt">tooltip_text =</span> tooltips)</code></pre></div>
+<div id="htmlwidget-f701c3aa6e8aebac235a" style="width:480px;height:288px;" class="scatterD3 html-widget"></div>
+<script type="application/json" data-for="htmlwidget-f701c3aa6e8aebac235a">{"x":{"data":{"x":[2.62,2.875,2.32,3.215,3.44,3.46,3.57,3.19,3.15,3.44,3.44,4.07,3.73,3.78,5.25,5.424,5.345,2.2,1.615,1.835,2.465,3.52,3.435,3.84,3.845,1.935,2.14,1.513,3.17,2.77,3.57,2.78],"y":[21,21,22.8,21.4,18.7,18.1,14.3,24.4,22.8,19.2,17.8,16.4,17.3,15.2,10.4,10.4,14.7,32.4,30.4,33.9,21.5,15.5,15.2,13.3,19.2,27.3,26,30.4,15.8,19.7,15,21.4],"key_var":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,2 [...]
<p>You can also disable tooltips entirely with <code>tooltips = FALSE</code>.</p>
</div>
+<div id="open-urls-when-clicking-points" class="section level2">
+<h2>Open URLs when clicking points</h2>
+<p>With the <code>url_var</code> argument, you can specify a character vectors of URLs, associated to each point, and which will be opened when the point is clicked.</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">mtcars$urls <-<span class="st"> </span><span class="kw">paste0</span>(<span class="st">"https://www.duckduckgo.com/?q="</span>, <span class="kw">rownames</span>(mtcars))
+<span class="kw">scatterD3</span>(<span class="dt">data =</span> mtcars, <span class="dt">x =</span> wt, <span class="dt">y =</span> mpg, <span class="dt">lab =</span> names, <span class="dt">url_var =</span> urls)</code></pre></div>
+<div id="htmlwidget-0d0f819ba07f053d5221" style="width:480px;height:288px;" class="scatterD3 html-widget"></div>
+<script type="application/json" data-for="htmlwidget-0d0f819ba07f053d5221">{"x":{"data":{"x":[2.62,2.875,2.32,3.215,3.44,3.46,3.57,3.19,3.15,3.44,3.44,4.07,3.73,3.78,5.25,5.424,5.345,2.2,1.615,1.835,2.465,3.52,3.435,3.84,3.845,1.935,2.14,1.513,3.17,2.77,3.57,2.78],"y":[21,21,22.8,21.4,18.7,18.1,14.3,24.4,22.8,19.2,17.8,16.4,17.3,15.2,10.4,10.4,14.7,32.4,30.4,33.9,21.5,15.5,15.2,13.3,19.2,27.3,26,30.4,15.8,19.7,15,21.4],"lab":["Mazda RX4","Mazda RX4 Wag","Datsun 710","Hornet 4 Drive","Hor [...]
+<p>Note that this won’t work inside RStudio’s internal browser.</p>
+</div>
+<div id="javascript-callback-on-clicking-point" class="section level2">
+<h2>JavaScript callback on clicking point</h2>
+<p>The optional <code>click_callback</code> argument is a character string defining a JavaScript function to be called when a dot is clicked. It must accept two arguments : <code>html_id</code> (the unique <code>id</code> of the current scatterplot), and <code>i</code> (the index of the clicked point).</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">scatterD3</span>(<span class="dt">data =</span> mtcars, <span class="dt">x =</span> wt, <span class="dt">y =</span> mpg,
+ <span class="dt">click_callback =</span> <span class="st">"function(id, index) {</span>
+<span class="st"> alert('scatterplot ID: ' + id + ' - Point index: ' + index) </span>
+<span class="st"> }"</span>)</code></pre></div>
+<div id="htmlwidget-d0b58445b163a8c127f2" style="width:480px;height:288px;" class="scatterD3 html-widget"></div>
+<script type="application/json" data-for="htmlwidget-d0b58445b163a8c127f2">{"x":{"data":{"x":[2.62,2.875,2.32,3.215,3.44,3.46,3.57,3.19,3.15,3.44,3.44,4.07,3.73,3.78,5.25,5.424,5.345,2.2,1.615,1.835,2.465,3.52,3.435,3.84,3.845,1.935,2.14,1.513,3.17,2.77,3.57,2.78],"y":[21,21,22.8,21.4,18.7,18.1,14.3,24.4,22.8,19.2,17.8,16.4,17.3,15.2,10.4,10.4,14.7,32.4,30.4,33.9,21.5,15.5,15.2,13.3,19.2,27.3,26,30.4,15.8,19.7,15,21.4],"key_var":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,2 [...]
+<p>One usage can be to pass the index of the clicked point back to Shiny when <code>scatterD3</code> is run inside a Shiny app. The following implementation can do it by using <code>Shiny.onInputChange()</code> :</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">scatterD3</span>(<span class="dt">data =</span> mtcars, <span class="dt">x =</span> wt, <span class="dt">y =</span> mpg,
+ <span class="dt">click_callback =</span> <span class="st">"function(id, index) {</span>
+<span class="st"> if(id && typeof(Shiny) != 'undefined') {</span>
+<span class="st"> Shiny.onInputChange(id + '_selected', index);</span>
+<span class="st"> }</span>
+<span class="st">}"</span>)</code></pre></div>
+<p>Thanks to <a href="https://github.com/detule">detule</a> and <a href="https://github.com/harveyl888">harveyl888</a> for the code.</p>
+<p>Note that <code>url_var</code> and <code>click_callback</code> cannot be used at the same time.</p>
+</div>
+<div id="javascript-zoom-callback" class="section level2">
+<h2>JavaScript zoom callback</h2>
+<p>The optional <code>zoom_callback</code> argument is a character string defining a JavaScript function to be called when a zoom event is triggered. It must accept two arguments <code>xmin</code>, <code>xmax</code>, <code>ymin</code> and <code>ymax</code> (in this order), which give the new <code>x</code> and <code>y</code> domains after zooming.</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">scatterD3</span>(<span class="dt">data =</span> mtcars, <span class="dt">x =</span> wt, <span class="dt">y =</span> mpg,
+ <span class="dt">zoom_callback =</span> <span class="st">"function(xmin, xmax, ymin, ymax) {</span>
+<span class="st"> var zoom = '<strong>Zoom</strong><br />xmin = ' + xmin + '<br />xmax = ' + xmax + '<br />ymin = ' + ymin + '<br />ymax = ' + ymax;</span>
+<span class="st"> document.getElementById('zoomExample').innerHTML = zoom;</span>
+<span class="st"> }"</span>)</code></pre></div>
+<div id="htmlwidget-47f949059d47adb212fd" style="width:480px;height:288px;" class="scatterD3 html-widget"></div>
+<script type="application/json" data-for="htmlwidget-47f949059d47adb212fd">{"x":{"data":{"x":[2.62,2.875,2.32,3.215,3.44,3.46,3.57,3.19,3.15,3.44,3.44,4.07,3.73,3.78,5.25,5.424,5.345,2.2,1.615,1.835,2.465,3.52,3.435,3.84,3.845,1.935,2.14,1.513,3.17,2.77,3.57,2.78],"y":[21,21,22.8,21.4,18.7,18.1,14.3,24.4,22.8,19.2,17.8,16.4,17.3,15.2,10.4,10.4,14.7,32.4,30.4,33.9,21.5,15.5,15.2,13.3,19.2,27.3,26,30.4,15.8,19.7,15,21.4],"key_var":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,2 [...]
+<div id="zoomExample" style="font-size: 80%; background-color: #F9F9F9; padding: 5px; margin-left: 5em; width: 15em;">
+<strong>Zoom</strong><br /> None yet !
+</div>
+</div>
<div id="confidence-ellipses" class="section level2">
<h2>Confidence ellipses</h2>
<p>You can draw a confidence ellipse around the points :</p>
-<pre class="sourceCode r"><code class="sourceCode r"><span class="kw">scatterD3</span>(<span class="dt">x =</span> mtcars$wt, <span class="dt">y =</span> mtcars$mpg, <span class="dt">ellipses =</span> <span class="ot">TRUE</span>)</code></pre>
-<p><div id="htmlwidget-4279" style="width:480px;height:288px;" class="scatterD3 html-widget"></div>
-<script type="application/json" data-for="htmlwidget-4279">{"x":{"data":{"x":[2.62,2.875,2.32,3.215,3.44,3.46,3.57,3.19,3.15,3.44,3.44,4.07,3.73,3.78,5.25,5.424,5.345,2.2,1.615,1.835,2.465,3.52,3.435,3.84,3.845,1.935,2.14,1.513,3.17,2.77,3.57,2.78],"y":[21,21,22.8,21.4,18.7,18.1,14.3,24.4,22.8,19.2,17.8,16.4,17.3,15.2,10.4,10.4,14.7,32.4,30.4,33.9,21.5,15.5,15.2,13.3,19.2,27.3,26,30.4,15.8,19.7,15,21.4],"point_opacity":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],"ke [...]
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">scatterD3</span>(<span class="dt">data =</span> mtcars, <span class="dt">x =</span> wt, <span class="dt">y =</span> mpg, <span class="dt">ellipses =</span> <span class="ot">TRUE</span>)</code></pre></div>
+<div id="htmlwidget-b8a2fa4553697669c372" style="width:480px;height:288px;" class="scatterD3 html-widget"></div>
+<script type="application/json" data-for="htmlwidget-b8a2fa4553697669c372">{"x":{"data":{"x":[2.62,2.875,2.32,3.215,3.44,3.46,3.57,3.19,3.15,3.44,3.44,4.07,3.73,3.78,5.25,5.424,5.345,2.2,1.615,1.835,2.465,3.52,3.435,3.84,3.845,1.935,2.14,1.513,3.17,2.77,3.57,2.78],"y":[21,21,22.8,21.4,18.7,18.1,14.3,24.4,22.8,19.2,17.8,16.4,17.3,15.2,10.4,10.4,14.7,32.4,30.4,33.9,21.5,15.5,15.2,13.3,19.2,27.3,26,30.4,15.8,19.7,15,21.4],"key_var":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,2 [...]
<p>Or around the different groups of points defined by <code>col_var</code> :</p>
-<pre class="sourceCode r"><code class="sourceCode r"><span class="kw">scatterD3</span>(<span class="dt">x =</span> mtcars$wt, <span class="dt">y =</span> mtcars$mpg, <span class="dt">col_var =</span> mtcars$cyl, <span class="dt">ellipses =</span> <span class="ot">TRUE</span>)</code></pre>
-<p><div id="htmlwidget-2030" style="width:480px;height:288px;" class="scatterD3 html-widget"></div>
-<script type="application/json" data-for="htmlwidget-2030">{"x":{"data":{"x":[2.62,2.875,2.32,3.215,3.44,3.46,3.57,3.19,3.15,3.44,3.44,4.07,3.73,3.78,5.25,5.424,5.345,2.2,1.615,1.835,2.465,3.52,3.435,3.84,3.845,1.935,2.14,1.513,3.17,2.77,3.57,2.78],"y":[21,21,22.8,21.4,18.7,18.1,14.3,24.4,22.8,19.2,17.8,16.4,17.3,15.2,10.4,10.4,14.7,32.4,30.4,33.9,21.5,15.5,15.2,13.3,19.2,27.3,26,30.4,15.8,19.7,15,21.4],"point_opacity":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],"co [...]
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">scatterD3</span>(<span class="dt">data =</span> mtcars, <span class="dt">x =</span> wt, <span class="dt">y =</span> mpg, <span class="dt">col_var =</span> cyl, <span class="dt">ellipses =</span> <span class="ot">TRUE</span>)</code></pre></div>
+<div id="htmlwidget-6ee123d8c724af091a73" style="width:480px;height:288px;" class="scatterD3 html-widget"></div>
+<script type="application/json" data-for="htmlwidget-6ee123d8c724af091a73">{"x":{"data":{"x":[2.62,2.875,2.32,3.215,3.44,3.46,3.57,3.19,3.15,3.44,3.44,4.07,3.73,3.78,5.25,5.424,5.345,2.2,1.615,1.835,2.465,3.52,3.435,3.84,3.845,1.935,2.14,1.513,3.17,2.77,3.57,2.78],"y":[21,21,22.8,21.4,18.7,18.1,14.3,24.4,22.8,19.2,17.8,16.4,17.3,15.2,10.4,10.4,14.7,32.4,30.4,33.9,21.5,15.5,15.2,13.3,19.2,27.3,26,30.4,15.8,19.7,15,21.4],"col_var":["6","6","4","6","8","6","8","4","4","6","6","8","8","8","8 [...]
<p>Ellipses are computed by the <code>ellipse.default()</code> function of the <a href="https://cran.r-project.org/package=ellipse">ellipse package</a>. The confidence level can be changed with the <code>ellipse_level</code> argument (<code>0.95</code> by default).</p>
</div>
+<div id="gear-menu" class="section level2">
+<h2>Gear menu</h2>
+<p>The “gear menu” is a small menu which can be displayed by clocking on the “gear” icon on the top-right corner of the plot. It allows to reset the zoom, export the current graph to SVG, and toggle lasso selection.</p>
+<p>It is displayed by default, but you can hide it with the <code>menu = FALSE</code> argument.</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">scatterD3</span>(<span class="dt">data =</span> mtcars, <span class="dt">x =</span> wt, <span class="dt">y =</span> mpg, <span class="dt">menu =</span> <span class="ot">FALSE</span>)</code></pre></div>
+<div id="htmlwidget-f74d591ea9e0cc941066" style="width:480px;height:288px;" class="scatterD3 html-widget"></div>
+<script type="application/json" data-for="htmlwidget-f74d591ea9e0cc941066">{"x":{"data":{"x":[2.62,2.875,2.32,3.215,3.44,3.46,3.57,3.19,3.15,3.44,3.44,4.07,3.73,3.78,5.25,5.424,5.345,2.2,1.615,1.835,2.465,3.52,3.435,3.84,3.845,1.935,2.14,1.513,3.17,2.77,3.57,2.78],"y":[21,21,22.8,21.4,18.7,18.1,14.3,24.4,22.8,19.2,17.8,16.4,17.3,15.2,10.4,10.4,14.7,32.4,30.4,33.9,21.5,15.5,15.2,13.3,19.2,27.3,26,30.4,15.8,19.7,15,21.4],"key_var":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,2 [...]
+</div>
<div id="lasso-selection-tool" class="section level2">
<h2>Lasso selection tool</h2>
<p>Thanks to the <a href="https://github.com/skokenes/D3-Lasso-Plugin">d3-lasso-plugin</a> integration made by @<a href="https://github.com/timelyportfolio">timelyportfolio</a>, you can select and highlight points with a lasso selection tool. To activate it, just add a <code>lasso = TRUE</code> argument. The tool is used by shift-clicking and dragging on the plot area (if it doesn’t activate, click on the chart first to give it focus).</p>
-<pre class="sourceCode r"><code class="sourceCode r"><span class="kw">scatterD3</span>(<span class="dt">x =</span> mtcars$wt, <span class="dt">y =</span> mtcars$mpg, <span class="dt">lab =</span> <span class="kw">rownames</span>(mtcars), <span class="dt">lasso =</span> <span class="ot">TRUE</span>)</code></pre>
-<p><div id="htmlwidget-2464" style="width:480px;height:288px;" class="scatterD3 html-widget"></div>
-<script type="application/json" data-for="htmlwidget-2464">{"x":{"data":{"x":[2.62,2.875,2.32,3.215,3.44,3.46,3.57,3.19,3.15,3.44,3.44,4.07,3.73,3.78,5.25,5.424,5.345,2.2,1.615,1.835,2.465,3.52,3.435,3.84,3.845,1.935,2.14,1.513,3.17,2.77,3.57,2.78],"y":[21,21,22.8,21.4,18.7,18.1,14.3,24.4,22.8,19.2,17.8,16.4,17.3,15.2,10.4,10.4,14.7,32.4,30.4,33.9,21.5,15.5,15.2,13.3,19.2,27.3,26,30.4,15.8,19.7,15,21.4],"lab":["Mazda RX4","Mazda RX4 Wag","Datsun 710","Hornet 4 Drive","Hornet Sportabout", [...]
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">mtcars$names <-<span class="st"> </span><span class="kw">rownames</span>(mtcars)
+<span class="kw">scatterD3</span>(<span class="dt">data =</span> mtcars, <span class="dt">x =</span> wt, <span class="dt">y =</span> mpg, <span class="dt">lab =</span> names, <span class="dt">lasso =</span> <span class="ot">TRUE</span>)</code></pre></div>
+<div id="htmlwidget-7d40d1c7d9417b69b344" style="width:480px;height:288px;" class="scatterD3 html-widget"></div>
+<script type="application/json" data-for="htmlwidget-7d40d1c7d9417b69b344">{"x":{"data":{"x":[2.62,2.875,2.32,3.215,3.44,3.46,3.57,3.19,3.15,3.44,3.44,4.07,3.73,3.78,5.25,5.424,5.345,2.2,1.615,1.835,2.465,3.52,3.435,3.84,3.845,1.935,2.14,1.513,3.17,2.77,3.57,2.78],"y":[21,21,22.8,21.4,18.7,18.1,14.3,24.4,22.8,19.2,17.8,16.4,17.3,15.2,10.4,10.4,14.7,32.4,30.4,33.9,21.5,15.5,15.2,13.3,19.2,27.3,26,30.4,15.8,19.7,15,21.4],"lab":["Mazda RX4","Mazda RX4 Wag","Datsun 710","Hornet 4 Drive","Hor [...]
<p>To undo the selection, just shift-click again.</p>
<p>You can specify a custom JavaScript callback function to be called by passing it to the <code>lasso_callback</code> argument as a character string. This function should accept a <code>sel</code> argument, which is a d3 selection of selected points.</p>
<p>Here is an example which shows an alert with selected point labels :</p>
-<pre class="sourceCode r"><code class="sourceCode r"><span class="kw">scatterD3</span>(<span class="dt">x =</span> mtcars$wt, <span class="dt">y =</span> mtcars$mpg, <span class="dt">lab =</span> <span class="kw">rownames</span>(mtcars),
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">mtcars$names <-<span class="st"> </span><span class="kw">rownames</span>(mtcars)
+<span class="kw">scatterD3</span>(<span class="dt">data =</span> mtcars,
+ <span class="dt">x =</span> wt, <span class="dt">y =</span> mpg, <span class="dt">lab =</span> names,
<span class="dt">lasso =</span> <span class="ot">TRUE</span>,
- <span class="dt">lasso_callback =</span> <span class="st">"function(sel) {alert(sel.data().map(function(d) {return d.lab}).join('</span><span class="ch">\\</span><span class="st">n'));}"</span>)</code></pre>
-<p><div id="htmlwidget-7477" style="width:480px;height:288px;" class="scatterD3 html-widget"></div>
-<script type="application/json" data-for="htmlwidget-7477">{"x":{"data":{"x":[2.62,2.875,2.32,3.215,3.44,3.46,3.57,3.19,3.15,3.44,3.44,4.07,3.73,3.78,5.25,5.424,5.345,2.2,1.615,1.835,2.465,3.52,3.435,3.84,3.845,1.935,2.14,1.513,3.17,2.77,3.57,2.78],"y":[21,21,22.8,21.4,18.7,18.1,14.3,24.4,22.8,19.2,17.8,16.4,17.3,15.2,10.4,10.4,14.7,32.4,30.4,33.9,21.5,15.5,15.2,13.3,19.2,27.3,26,30.4,15.8,19.7,15,21.4],"lab":["Mazda RX4","Mazda RX4 Wag","Datsun 710","Hornet 4 Drive","Hornet Sportabout", [...]
+ <span class="dt">lasso_callback =</span> <span class="st">"function(sel) {alert(sel.data().map(function(d) {return d.lab}).join('</span><span class="ch">\\</span><span class="st">n'));}"</span>)</code></pre></div>
+<div id="htmlwidget-46c5d2d5e0dbca1886cf" style="width:480px;height:288px;" class="scatterD3 html-widget"></div>
+<script type="application/json" data-for="htmlwidget-46c5d2d5e0dbca1886cf">{"x":{"data":{"x":[2.62,2.875,2.32,3.215,3.44,3.46,3.57,3.19,3.15,3.44,3.44,4.07,3.73,3.78,5.25,5.424,5.345,2.2,1.615,1.835,2.465,3.52,3.435,3.84,3.845,1.935,2.14,1.513,3.17,2.77,3.57,2.78],"y":[21,21,22.8,21.4,18.7,18.1,14.3,24.4,22.8,19.2,17.8,16.4,17.3,15.2,10.4,10.4,14.7,32.4,30.4,33.9,21.5,15.5,15.2,13.3,19.2,27.3,26,30.4,15.8,19.7,15,21.4],"lab":["Mazda RX4","Mazda RX4 Wag","Datsun 710","Hornet 4 Drive","Hor [...]
+</div>
+<div id="custom-labels-positions-export" class="section level2">
+<h2>Custom labels positions export</h2>
+<p>The “gear menu” allows to export the current custom labels position as a CSV file for later reuse.</p>
+<p>For example, if you change the labels placement in the following plot :</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">mtcars$names <-<span class="st"> </span><span class="kw">rownames</span>(mtcars)
+<span class="kw">scatterD3</span>(<span class="dt">data =</span> mtcars, <span class="dt">x =</span> wt, <span class="dt">y =</span> mpg, <span class="dt">lab =</span> names)</code></pre></div>
+<div id="htmlwidget-c3d3dddc0a556cf097ff" style="width:480px;height:288px;" class="scatterD3 html-widget"></div>
+<script type="application/json" data-for="htmlwidget-c3d3dddc0a556cf097ff">{"x":{"data":{"x":[2.62,2.875,2.32,3.215,3.44,3.46,3.57,3.19,3.15,3.44,3.44,4.07,3.73,3.78,5.25,5.424,5.345,2.2,1.615,1.835,2.465,3.52,3.435,3.84,3.845,1.935,2.14,1.513,3.17,2.77,3.57,2.78],"y":[21,21,22.8,21.4,18.7,18.1,14.3,24.4,22.8,19.2,17.8,16.4,17.3,15.2,10.4,10.4,14.7,32.4,30.4,33.9,21.5,15.5,15.2,13.3,19.2,27.3,26,30.4,15.8,19.7,15,21.4],"lab":["Mazda RX4","Mazda RX4 Wag","Datsun 710","Hornet 4 Drive","Hor [...]
+<p>You can then open the menu and select <em>Export labels positions</em> to save them into a CSV file. If you want to use these positions in another plot, you can do something like that :</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">labels <-<span class="st"> </span><span class="kw">read.csv</span>(<span class="st">"scatterD3_labels.csv"</span>)
+<span class="kw">library</span>(ggplot2)
+<span class="kw">ggplot</span>() +
+<span class="st"> </span><span class="kw">geom_point</span>(<span class="dt">data =</span> mtcars, <span class="kw">aes</span>(<span class="dt">x=</span>wt, <span class="dt">y=</span>mpg)) +
+<span class="st"> </span><span class="kw">geom_text</span>(<span class="dt">data =</span> labels,
+ <span class="kw">aes</span>(<span class="dt">x =</span> scatterD3_label_x,
+ <span class="dt">y =</span> scatterD3_label_y,
+ <span class="dt">label =</span> scatterD3_label))</code></pre></div>
</div>
<div id="other-options" class="section level2">
<h2>Other options</h2>
<p>Finally, and for more specific use cases, you can represent some points as an arrow starting from the origin by using the <code>type_var</code> argument, and you can add a unit circle with <code>unit_circle = TRUE</code>.</p>
-<pre class="sourceCode r"><code class="sourceCode r"><span class="kw">scatterD3</span>(<span class="dt">x =</span> <span class="kw">c</span>(<span class="dv">1</span>, <span class="fl">0.9</span>, <span class="fl">0.7</span>, <span class="fl">0.2</span>, -<span class="fl">0.4</span>, -<span class="fl">0.5</span>), <span class="dt">xlab =</span> <span class="st">"x"</span>,
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">scatterD3</span>(<span class="dt">x =</span> <span class="kw">c</span>(<span class="dv">1</span>, <span class="fl">0.9</span>, <span class="fl">0.7</span>, <span class="fl">0.2</span>, -<span class="fl">0.4</span>, -<span class="fl">0.5</span>), <span class="dt">xlab =</span> <span class="st">"x"</span>,
<span class="dt">y =</span> <span class="kw">c</span>(<span class="dv">1</span>, <span class="fl">0.1</span>, -<span class="fl">0.5</span>, <span class="fl">0.5</span>, -<span class="fl">0.6</span>, <span class="fl">0.7</span>), <span class="dt">ylab =</span> <span class="st">"y"</span>,
<span class="dt">lab =</span> LETTERS[<span class="dv">1</span>:<span class="dv">6</span>], <span class="dt">type_var =</span> <span class="kw">c</span>(<span class="st">"point"</span>, <span class="kw">rep</span>(<span class="st">"arrow"</span>, <span class="dv">5</span>)),
- <span class="dt">unit_circle =</span> <span class="ot">TRUE</span>, <span class="dt">fixed =</span> <span class="ot">TRUE</span>, <span class="dt">xlim =</span> <span class="kw">c</span>(-<span class="fl">1.2</span>, <span class="fl">1.2</span>))</code></pre>
-<p><div id="htmlwidget-4340" style="width:480px;height:288px;" class="scatterD3 html-widget"></div>
-<script type="application/json" data-for="htmlwidget-4340">{"x":{"data":{"x":[1,0.9,0.7,0.2,-0.4,-0.5],"y":[1,0.1,-0.5,0.5,-0.6,0.7],"lab":["A","B","C","D","E","F"],"point_opacity":[1,1,1,1,1,1],"type_var":["point","arrow","arrow","arrow","arrow","arrow"],"key_var":[1,2,3,4,5,6]},"settings":{"labels_size":10,"point_size":64,"xlab":"x","ylab":"y","has_labels":true,"col_var":null,"col_lab":"NULL","colors":null,"ellipses":false,"ellipses_data":[],"symbol_var":null,"symbol_lab":"NULL","size_ [...]
+ <span class="dt">unit_circle =</span> <span class="ot">TRUE</span>, <span class="dt">fixed =</span> <span class="ot">TRUE</span>, <span class="dt">xlim =</span> <span class="kw">c</span>(-<span class="fl">1.2</span>, <span class="fl">1.2</span>))</code></pre></div>
+<div id="htmlwidget-9cac3ce23d55a34a9f68" style="width:480px;height:288px;" class="scatterD3 html-widget"></div>
+<script type="application/json" data-for="htmlwidget-9cac3ce23d55a34a9f68">{"x":{"data":{"x":[1,0.9,0.7,0.2,-0.4,-0.5],"y":[1,0.1,-0.5,0.5,-0.6,0.7],"lab":["A","B","C","D","E","F"],"type_var":["point","arrow","arrow","arrow","arrow","arrow"],"key_var":[1,2,3,4,5,6]},"settings":{"labels_size":10,"point_size":64,"point_opacity":1,"hover_size":1,"hover_opacity":null,"xlab":"x","ylab":"y","has_labels":true,"col_lab":"NULL","col_continuous":false,"colors":null,"ellipses":false,"ellipses_data" [...]
</div>
<div id="shiny-integration" class="section level2">
<h2>Shiny integration</h2>
@@ -198,13 +390,14 @@ code > span.er { color: #ff0000; font-weight: bold; }
</div>
<div id="additional-controls-reset-zoom-and-svg-export" class="section level3">
<h3>Additional controls : Reset zoom and SVG export</h3>
-<p>Furthermore, <code>scatterD3</code> provides some additional handlers for two interactive features : SVG export and zoom resetting.</p>
+<p>Furthermore, <code>scatterD3</code> provides some additional handlers for three interactive features : SVG export, zoom resetting and lasso selection. Those are already accessible via the “gear menu”, but you may want to replace it with custom form controls.</p>
<p>By default, you just have to give the following <code>id</code> to the corresponding form controls :</p>
<ul>
<li><code>#scatterD3-reset-zoom</code> : reset zoom to default on click</li>
<li><code>#scatterD3-svg-export</code> : link to download the currently displayed figure as an SVG file</li>
+<li><code>#scatterD3-lasso-toggle</code> : toggle lasso selection</li>
</ul>
-<p>If you are not happy with these ids, you can specify their names yourself with the arguments <code>dom_id_svg_export</code> and <code>dom_id_reset_zoom</code>.</p>
+<p>If you are not happy with these ids, you can specify their names yourself with the arguments <code>dom_id_svg_export</code>, <code>dom_id_reset_zoom</code> and <code>dom_id_toggle</code>.</p>
</div>
<div id="sample-app-and-source-code" class="section level3">
<h3>Sample app and source code</h3>
diff --git a/inst/htmlwidgets/lib/d3-lasso-plugin/lasso.js b/inst/htmlwidgets/lib/d3-lasso-plugin/lasso.js
index 3c65c32..b1fdc43 100644
--- a/inst/htmlwidgets/lib/d3-lasso-plugin/lasso.js
+++ b/inst/htmlwidgets/lib/d3-lasso-plugin/lasso.js
@@ -9,9 +9,10 @@ d3.lasso = function() {
area = null,
on = {start:function(){}, draw: function(){}, end: function(){}};
- function lasso() {
+ function lasso(selection) {
+
// the element where the lasso was called
- var _this = d3.select(this[0][0]);
+ var _this = selection;
// add a new group for the lasso
var g = _this.append("g")
@@ -56,11 +57,17 @@ d3.lasso = function() {
var path_length_start;
// Apply drag behaviors
- var drag = d3.behavior.drag()
- .on("dragstart", dragstart)
+ var drag = d3.drag()
+ .on("start", dragstart)
.on("drag", dragmove)
- .on("dragend", dragend);
-
+ .on("end", dragend);
+
+ // Init DOM-local data
+ var right_edges = d3.local();
+ var left_edges = d3.local();
+ var close_right_edges = d3.local();
+ var close_left_edges = d3.local();
+
// Call drag
area.call(drag);
@@ -70,29 +77,32 @@ d3.lasso = function() {
tpath = "";
dyn_path.attr("d", "M0 0");
close_path.attr("d", "M0 0");
-
+
// Set path length start
path_length_start = 0;
// Set every item to have a false selection and reset their center point and counters
- items[0].forEach(function(d) {
+ items.each(function(d) {
d.hoverSelected = false;
d.loopSelected = false;
- var box = d.getBoundingClientRect();
- d.lassoPoint = {
- cx: Math.round(box.left + box.width/2),
- cy: Math.round(box.top + box.height/2),
- edges: {top:0,right:0,bottom:0,left:0},
- close_edges: {left: 0, right: 0}
- };
-
-
+ // Ignore text labels for center coordinates
+ if (this.tagName != "text") {
+ var box = this.getBoundingClientRect();
+ d.lassoPoint = {
+ cx: Math.round(box.left + box.width/2),
+ cy: Math.round(box.top + box.height/2)
+ };
+ }
+ right_edges.set(this, 0);
+ left_edges.set(this, 0);
+ close_right_edges.set(this, 0);
+ close_left_edges.set(this, 0);
});
// if hover is on, add hover function
if(hoverSelect===true) {
items.on("mouseover.lasso",function() {
// if hovered, change lasso selection attribute to true
- d3.select(this)[0][0].hoverSelected = true;
+ d3.select(this).hoverSelected = true;
});
}
@@ -127,8 +137,9 @@ d3.lasso = function() {
}
// Reset closed edges counter
- items[0].forEach(function(d) {
- d.lassoPoint.close_edges = {left:0,right:0};
+ items.each(function(d) {
+ close_left_edges.set(this, 0);
+ close_right_edges.set(this, 0);
});
// Calculate the current distance from the lasso origin
@@ -172,17 +183,16 @@ d3.lasso = function() {
var cur_pos = path_node.getPointAtLength(i);
var cur_pos_obj = {
x:Math.round(cur_pos.x*100)/100,
- y:Math.round(cur_pos.y*100)/100,
+ y:Math.round(cur_pos.y*100)/100
};
// Get the prior coordinates on the path
var prior_pos = path_node.getPointAtLength(i-1);
var prior_pos_obj = {
x:Math.round(prior_pos.x*100)/100,
- y:Math.round(prior_pos.y*100)/100,
+ y:Math.round(prior_pos.y*100)/100
};
-
// Iterate through each item
- items[0].filter(function(d) {
+ items.filter(function(d) {
var a;
// If we are on the same y position as the item and we weren't on this y before,
// mark as the last known point. Return false - we don't need to count an edge yet
@@ -214,59 +224,57 @@ d3.lasso = function() {
a = sign(d.lassoPoint.cy-cur_pos_obj.y)!=sign(d.lassoPoint.cy-prior_pos_obj.y);
}
return a;
- }).forEach(function(d) {
+ }).each(function(d) {
// Iterate through each object and add an edge to the left or right
if(cur_pos_obj.x>d.lassoPoint.cx) {
- d.lassoPoint.edges.right = d.lassoPoint.edges.right+1;
+ right_edges.set(this, right_edges.get(this) + 1);
}
if(cur_pos_obj.x<d.lassoPoint.cx) {
- d.lassoPoint.edges.left = d.lassoPoint.edges.left+1;
+ left_edges.set(this, left_edges.get(this) + 1);
}
- });
+ });
}
// If the path is closed and close select is set to true, draw the closed paths and count edges
if(isPathClosed === true && closePathSelect === true) {
close_path.attr("d",close_draw_path);
- close_path_node =calc_close_path.node();
+ var close_path_node = calc_close_path.node();
var close_path_length = close_path_node.getTotalLength();
- var close_path_edges = {left:0,right:0};
for (var i = 0; i<=close_path_length; i++) {
var cur_pos = close_path_node.getPointAtLength(i);
var prior_pos = close_path_node.getPointAtLength(i-1);
- items[0].filter(function(d) {return d.lassoPoint.cy==Math.round(cur_pos.y);}).forEach(function(d) {
+ items.filter(function(d) {return d.lassoPoint.cy==Math.round(cur_pos.y);}).each(function(d) {
if(Math.round(cur_pos.y)!=Math.round(prior_pos.y) && Math.round(cur_pos.x)>d.lassoPoint.cx) {
- d.lassoPoint.close_edges.right = 1;
+ close_right_edges.set(this, 1);
}
if(Math.round(cur_pos.y)!=Math.round(prior_pos.y) && Math.round(cur_pos.x)<d.lassoPoint.cx) {
- d.lassoPoint.close_edges.left = 1;
+ close_left_edges.set(this, 1);
}
});
}
// Check and see if the points have at least one edge to the left, and an odd # of edges to the right. If so, mark as selected.
- items[0].forEach(function(a) {
- if((a.lassoPoint.edges.left+a.lassoPoint.close_edges.left)>0 && (a.lassoPoint.edges.right + a.lassoPoint.close_edges.right)%2 ==1) {
+ items.each(function(a) {
+ if((left_edges.get(this) + close_left_edges.get(this))>0 && (right_edges.get(this) + close_right_edges.get(this)) % 2 == 1) {
a.loopSelected = true;
}
- else {
+ else {
a.loopSelected = false;
- }
+ }
});
}
else {
- items[0].forEach(function(d) {
+ items.each(function(d) {
d.loopSelected = false;
});
}
-
// Tag possible items
- d3.selectAll(items[0].filter(function(d) {return (d.loopSelected && isPathClosed) || d.hoverSelected;}))
+ items.filter(function(d) {return (d.loopSelected && isPathClosed) || d.hoverSelected;})
.each(function(d) { d.possible = true;});
- d3.selectAll(items[0].filter(function(d) {return !((d.loopSelected && isPathClosed) || d.hoverSelected);}))
+ items.filter(function(d) {return !((d.loopSelected && isPathClosed) || d.hoverSelected);})
.each(function(d) {d.possible = false;});
on.draw();
@@ -304,8 +312,8 @@ d3.lasso = function() {
if (!arguments.length) return items;
items = _;
- items[0].forEach(function(d) {
- var item = d3.select(d);
+ items.each(function(d) {
+ var item = d3.select(this);
if(typeof item.datum() === 'undefined') {
item.datum({possible:false,selected:false});
}
diff --git a/inst/htmlwidgets/lib/scatterD3.css b/inst/htmlwidgets/lib/scatterD3.css
deleted file mode 100644
index fa15819..0000000
--- a/inst/htmlwidgets/lib/scatterD3.css
+++ /dev/null
@@ -1,29 +0,0 @@
-.scatterD3-tooltip {
- position: absolute;
- color: #222;
- background: #fff;
- padding: .5em;
- text-shadow: #f5f5f5 0 1px 0;
- border-radius: 2px;
- box-shadow: 0px 0px 2px 0px #a6a6a6;
- opacity: 0.9;
- font-family: sans-serif;
- font-size: 9px;
- z-index: 10;
-}
-
-.hidden {
- display: none;
-}
-
-.scatterD3 .point-label {
- cursor: pointer;
-}
-
-.scatterD3 .pane {
- cursor: move;
-}
-
-.scatterD3, .scatterD3:focus {
- outline: 0px solid transparent
-}
diff --git a/inst/htmlwidgets/scatterD3-arrows.js b/inst/htmlwidgets/scatterD3-arrows.js
new file mode 100644
index 0000000..3d99786
--- /dev/null
+++ b/inst/htmlwidgets/scatterD3-arrows.js
@@ -0,0 +1,62 @@
+
+function add_arrows_defs(svg, settings, scales) {
+ // <defs>
+ var defs = svg.append("defs");
+ // arrow head markers
+ scales.color.range().forEach(function(d) {
+ defs.append("marker")
+ .attr("id", "arrow-head-" + settings.html_id + "-" + d)
+ .attr("markerWidth", "10")
+ .attr("markerHeight", "10")
+ .attr("refX", "10")
+ .attr("refY", "4")
+ .attr("orient", "auto")
+ .append("path")
+ .attr("d", "M0,0 L0,8 L10,4 L0,0")
+ .style("fill", d);
+ });
+}
+
+
+// Arrow drawing function
+function draw_arrow(selection, scales) {
+ selection
+ .attr("x1", function(d) { return scales.x(0); })
+ .attr("y1", function(d) { return scales.y(0); })
+ .attr("x2", function(d) { return scales.x(d.x); })
+ .attr("y2", function(d) { return scales.y(d.y); });
+}
+
+// Initial arrow attributes
+function arrow_init (selection, settings) {
+ // tooltips when hovering points
+ if (settings.has_tooltips) {
+ var tooltip = d3.select(".scatterD3-tooltip");
+ selection.on("mouseover", function(d, i){
+ tooltip.style("visibility", "visible")
+ .html(tooltip_content(d, settings));
+ });
+ selection.on("mousemove", function(){
+ tooltip.style("top", (d3.event.pageY+15)+"px").style("left",(d3.event.pageX+15)+"px");
+ });
+ selection.on("mouseout", function(){
+ tooltip.style("visibility", "hidden");
+ });
+ }
+}
+
+// Apply format to arrow
+function arrow_formatting(selection, settings, scales) {
+ var sel = selection
+ .call(function(sel) {draw_arrow(sel, scales);})
+ .style("stroke-width", "1px")
+ // stroke color
+ .style("stroke", function(d) { return scales.color(d.col_var); })
+ .attr("marker-end", function(d) { return "url(#arrow-head-" + settings.html_id + "-" + scales.color(d.col_var) + ")"; })
+ .attr("class", function(d,i) { return "arrow color color-c" + css_clean(d.col_var); })
+ .style("opacity", function(d) {
+ return d.opacity_var !== undefined ? scales.opacity(d.opacity_var) : settings.point_opacity;
+ });
+ return sel;
+}
+
diff --git a/inst/htmlwidgets/scatterD3-axes.js b/inst/htmlwidgets/scatterD3-axes.js
new file mode 100644
index 0000000..280f616
--- /dev/null
+++ b/inst/htmlwidgets/scatterD3-axes.js
@@ -0,0 +1,43 @@
+// Create and draw x and y axes
+function add_axes(selection, dims, settings, scales) {
+
+ // x axis
+ selection.append("g")
+ .attr("class", "x axis")
+ .attr("transform", "translate(0," + dims.height + ")")
+ .style("font-size", settings.axes_font_size)
+ .call(scales.xAxis);
+
+ selection.append("text")
+ .attr("class", "x-axis-label")
+ .attr("transform", "translate(" + (dims.width - 5) + "," + (dims.height - 6) + ")")
+ .style("text-anchor", "end")
+ .style("font-size", settings.axes_font_size)
+ .text(settings.xlab);
+
+ // y axis
+ selection.append("g")
+ .attr("class", "y axis")
+ .style("font-size", settings.axes_font_size)
+ .call(scales.yAxis);
+
+ selection.append("text")
+ .attr("class", "y-axis-label")
+ .attr("transform", "translate(5,6) rotate(-90)")
+ .attr("dy", ".71em")
+ .style("text-anchor", "end")
+ .text(settings.ylab);
+
+}
+
+// Add unit circle
+function add_unit_circle(selection, scales) {
+ selection
+ .attr('cx', scales.x(0))
+ .attr('cy', scales.y(0))
+ .attr('rx', scales.x(1)-scales.x(0))
+ .attr('ry', scales.y(0)-scales.y(1))
+ .style("stroke", "#888")
+ .style("fill", "none")
+ .style("opacity", "1");
+}
diff --git a/inst/htmlwidgets/scatterD3-dots.js b/inst/htmlwidgets/scatterD3-dots.js
new file mode 100644
index 0000000..0500b86
--- /dev/null
+++ b/inst/htmlwidgets/scatterD3-dots.js
@@ -0,0 +1,85 @@
+// Returns dot size from associated data
+function dot_size(data, settings, scales) {
+ var size = settings.point_size;
+ if (settings.has_size_var) { size = scales.size(data.size_var); }
+ return(size);
+}
+
+// Initial dot attributes
+function dot_init (selection, settings, scales) {
+ // tooltips when hovering points
+ var tooltip = d3.select(".scatterD3-tooltip");
+ selection.on("mouseover", function(d, i){
+ d3.select(this)
+ .transition().duration(150)
+ .attr("d", d3.symbol()
+ .type(function(d) { return d3.symbols[scales.symbol(d.symbol_var)]; })
+ .size(function(d) { return (dot_size(d, settings, scales) * settings.hover_size); })
+ )
+ .style("opacity", function(d) {
+ if (settings.hover_opacity !== null) {
+ return settings.hover_opacity;
+ } else {
+ return(d.opacity_var === undefined ? settings.point_opacity : scales.opacity(d.opacity_var));
+ }
+ });
+ if (settings.has_url_var) {
+ d3.select(this)
+ .style("cursor", function(d) {
+ return (d.url_var != "" ? "pointer" : "default");
+ });
+ }
+ if (settings.has_tooltips) {
+ tooltip.style("visibility", "visible")
+ .html(tooltip_content(d, settings));
+ }
+ });
+ selection.on("mousemove", function(){
+ if (settings.has_tooltips) {
+ tooltip.style("top", (d3.event.pageY+15)+"px").style("left",(d3.event.pageX+15)+"px");
+ }
+ });
+ selection.on("mouseout", function(){
+ d3.select(this)
+ .transition().duration(150)
+ .attr("d", d3.symbol()
+ .type(function(d) { return d3.symbols[scales.symbol(d.symbol_var)]; })
+ .size(function(d) { return dot_size(d, settings, scales);})
+ )
+ .style("opacity", function(d) {
+ return(d.opacity_var === undefined ? settings.point_opacity : scales.opacity(d.opacity_var));
+ });
+ if (settings.has_tooltips) {
+ tooltip.style("visibility", "hidden");
+ }
+ });
+ selection.on("click", function(d, i) {
+ if (typeof settings.click_callback === 'function') {
+ settings.click_callback(settings.html_id, i + 1);
+ }
+ if (settings.has_url_var && d.url_var != "") {
+ var win = window.open(d.url_var, '_blank');
+ win.focus();
+ }
+ });
+}
+
+// Apply format to dot
+function dot_formatting(selection, settings, scales) {
+ var sel = selection
+ .attr("transform", function(d) { return translation(d, scales); })
+ // fill color
+ .style("fill", function(d) { return scales.color(d.col_var); })
+ .style("opacity", function(d) {
+ return d.opacity_var !== undefined ? scales.opacity(d.opacity_var) : settings.point_opacity;
+ })
+ // symbol and size
+ .attr("d", d3.symbol()
+ .type(function(d) {return d3.symbols[scales.symbol(d.symbol_var)];})
+ .size(function(d) { return dot_size(d, settings, scales); })
+ )
+ .attr("class", function(d,i) {
+ return "dot symbol symbol-c" + css_clean(d.symbol_var) + " color color-c" + css_clean(d.col_var);
+ });
+ return sel;
+}
diff --git a/inst/htmlwidgets/scatterD3-ellipses.js b/inst/htmlwidgets/scatterD3-ellipses.js
new file mode 100644
index 0000000..871b57a
--- /dev/null
+++ b/inst/htmlwidgets/scatterD3-ellipses.js
@@ -0,0 +1,36 @@
+
+// Initial ellipse attributes
+function ellipse_init(selection) {
+ selection
+ .style("fill", "none");
+}
+
+// Apply format to ellipse
+function ellipse_formatting(selection, settings, scales) {
+
+ // Ellipses path function
+ var ellipseFunc = d3.line()
+ .x(function(d) { return scales.x(d.x); })
+ .y(function(d) { return scales.y(d.y); });
+
+ selection
+ .attr("d", function(d) {
+ var ell = HTMLWidgets.dataframeToD3(d.data);
+ return (ellipseFunc(ell));
+ })
+ .style("stroke", function(d) {
+ // Only one ellipse
+ if (d.level == "_scatterD3_all") {
+ if (settings.col_continuous) {
+ return(d3.interpolateViridis(0));
+ } else {
+ return(scales.color.range()[0]);
+ }
+ }
+ return( scales.color(d.level));
+ })
+ .style("opacity", 1)
+ .attr("class", function(d) {
+ return "ellipse color color-c" + css_clean(d.level);
+ });
+}
diff --git a/inst/htmlwidgets/scatterD3-exports.js b/inst/htmlwidgets/scatterD3-exports.js
new file mode 100644
index 0000000..6a47f46
--- /dev/null
+++ b/inst/htmlwidgets/scatterD3-exports.js
@@ -0,0 +1,37 @@
+// Export to SVG function
+function export_svg(sel, svg, settings) {
+ var svg_content = svg
+ .attr("xmlns", "http://www.w3.org/2000/svg")
+ .attr("version", 1.1)
+ .node().parentNode.innerHTML;
+ // Dirty dirty dirty...
+ var tmp = svg_content.replace(/<g class="gear-menu[\s\S]*?<\/g>/, '');
+ var svg_content2 = tmp.replace(/<ul class="scatterD3-menu[\s\S]*?<\/ul>/, '');
+ var image_data = "data:image/octet-stream;base64," + window.btoa(svg_content2);
+ d3.select(sel)
+ .attr("download", settings.html_id + ".svg")
+ .attr("href", image_data);
+}
+
+// Function to export custom labels position to CSV file
+function export_labels_position(sel, data, settings, scales) {
+ var lines_data = ["scatterD3_label,scatterD3_label_x,scatterD3_label_y"];
+ data.forEach(function(d, index){
+ var labx = d.x;
+ if (d.lab_dx !== undefined) {
+ labx = d.x + scales.x.invert(d.lab_dx) - scales.x.domain()[0];
+ }
+ var size = (d.size_var === undefined) ? settings.point_size : scales.size(d.size_var);
+ var offset_y = (-Math.sqrt(size) / 2) - 6;
+ if (d.lab_dy !== undefined) {
+ offset_y = d.lab_dy;
+ }
+ var laby = d.y + scales.y.invert(offset_y) - scales.y.domain()[1];
+ var this_line = d.lab + "," + labx + "," + laby;
+ lines_data.push(this_line);
+ });
+ var csv_content = "data:text/csv;base64," + btoa(lines_data.join("\n"));
+ d3.select(sel)
+ .attr("download", settings.html_id + "_labels.csv")
+ .attr("href", encodeURI(csv_content));
+}
diff --git a/inst/htmlwidgets/scatterD3-labels.js b/inst/htmlwidgets/scatterD3-labels.js
new file mode 100644
index 0000000..281efd6
--- /dev/null
+++ b/inst/htmlwidgets/scatterD3-labels.js
@@ -0,0 +1,36 @@
+
+// Initial text label attributes
+function label_init (selection) {
+ selection
+ .attr("text-anchor", "middle");
+}
+
+// Compute default vertical offset for labels
+function default_label_dy(size, y, type_var,settings) {
+ if (y < 0 && type_var !== undefined && type_var == "arrow") {
+ return (Math.sqrt(size) / 2) + settings.labels_size + 2;
+ }
+ else {
+ return (-Math.sqrt(size) / 2) - 6;
+ }
+}
+
+// Apply format to text label
+function label_formatting (selection, settings, scales) {
+ var sel = selection
+ .text(function(d) {return(d.lab);})
+ .style("font-size", settings.labels_size + "px")
+ .attr("class", function(d,i) { return "point-label color color-c" + css_clean(d.col_var) + " symbol symbol-c" + css_clean(d.symbol_var); })
+ .attr("transform", function(d) { return translation(d, scales); })
+ .style("fill", function(d) { return scales.color(d.col_var); })
+ .attr("dx", function(d) {
+ if (d.lab_dx === undefined) return("0px");
+ else return(d.lab_dx + "px");
+ })
+ .attr("dy", function(d) {
+ if (d.lab_dy !== undefined) return(d.lab_dy + "px");
+ var size = (d.size_var === undefined) ? settings.point_size : scales.size(d.size_var);
+ return default_label_dy(size, d.y, d.type_var, settings) + "px";
+ });
+ return sel;
+}
diff --git a/inst/htmlwidgets/scatterD3-lasso.js b/inst/htmlwidgets/scatterD3-lasso.js
new file mode 100644
index 0000000..e5673c1
--- /dev/null
+++ b/inst/htmlwidgets/scatterD3-lasso.js
@@ -0,0 +1,159 @@
+// Lasso functions to execute while lassoing
+var lasso_start = function(lasso) {
+ lasso.items()
+ .each(function(d){
+ if (d3.select(this).classed('dot')) {
+ d.scatterD3_lasso_dot_stroke = d.scatterD3_lasso_dot_stroke ? d.scatterD3_lasso_dot_stroke : d3.select(this).style("stroke");
+ d.scatterD3_lasso_dot_fill = d.scatterD3_lasso_dot_fill ? d.scatterD3_lasso_dot_fill : d3.select(this).style("fill");
+ d.scatterD3_lasso_dot_opacity = d.scatterD3_lasso_dot_opacity ? d.scatterD3_lasso_dot_opacity : d3.select(this).style("opacity");
+ }
+ if (d3.select(this).classed('arrow')) {
+ d.scatterD3_lasso_arrow_stroke = d.scatterD3_lasso_arrow_stroke ? d.scatterD3_lasso_arrow_stroke : d3.select(this).style("stroke");
+ d.scatterD3_lasso_arrow_fill = d.scatterD3_lasso_arrow_fill ? d.scatterD3_lasso_arrow_fill : d3.select(this).style("fill");
+ d.scatterD3_lasso_arrow_opacity = d.scatterD3_lasso_arrow_opacity ? d.scatterD3_lasso_arrow_opacity : d3.select(this).style("opacity");
+ }
+ if (d3.select(this).classed('point-label')) {
+ d.scatterD3_lasso_text_stroke = d.scatterD3_lasso_text_stroke ? d.scatterD3_lasso_text_stroke : d3.select(this).style("stroke");
+ d.scatterD3_lasso_text_fill = d.scatterD3_lasso_text_fill ? d.scatterD3_lasso_text_fill : d3.select(this).style("fill");
+ d.scatterD3_lasso_text_opacity = d.scatterD3_lasso_text_opacity ? d.scatterD3_lasso_text_opacity : d3.select(this).style("opacity");
+ }
+ })
+ .style("fill", null) // clear all of the fills
+ .style("opacity", null) // clear all of the opacities
+ .style("stroke", null) // clear all of the strokes
+ .classed("not-possible-lasso", true)
+ .classed("selected-lasso not-selected-lasso", false); // style as not possible
+};
+
+var lasso_draw = function(lasso) {
+ // Style the possible dots
+ lasso.items()
+ .filter(function(d) {return d.possible === true;})
+ .classed("not-possible-lasso", false)
+ .classed("possible-lasso", true);
+ // Style the not possible dot
+ lasso.items().filter(function(d) {return d.possible === false;})
+ .classed("not-possible-lasso", true)
+ .classed("possible-lasso", false);
+};
+
+var lasso_end = function(lasso, svg, settings, scales, zoom) {
+ lasso_off(svg, settings, zoom);
+ var some_selected = false;
+ if(lasso.items().filter(function(d) {return d.selected === true;}).size() !== 0){
+ some_selected = true;
+ }
+ // Reset the color of all dots
+ lasso.items()
+ .style("fill", function(d) {
+ if (d3.select(this).classed('point-label')) { return d.scatterD3_lasso_text_fill; }
+ if (d3.select(this).classed('dot')) { return d.scatterD3_lasso_dot_fill; }
+ if (d3.select(this).classed('arrow')) { return d.scatterD3_lasso_arrow_fill; }
+ return null;
+ })
+ .style("opacity", function(d) {
+ if (d3.select(this).classed('point-label')) { return d.scatterD3_lasso_text_opacity; }
+ if (d3.select(this).classed('dot')) { return d.scatterD3_lasso_dot_opacity; }
+ if (d3.select(this).classed('arrow')) { return d.scatterD3_lasso_arrow_opacity; }
+ return null;
+ })
+ .style("stroke", function(d) {
+ if (d3.select(this).classed('point-label')) { return d.scatterD3_lasso_text_stroke; }
+ if (d3.select(this).classed('dot')) { return d.scatterD3_lasso_dot_stroke; }
+ if (d3.select(this).classed('arrow')) { return d.scatterD3_lasso_arrow_stroke; }
+ return null;
+ });
+ if (some_selected) {
+ // Style the selected dots
+ var sel = lasso.items().filter(function(d) {return d.selected === true;})
+ .classed("not-possible-lasso possible-lasso", false)
+ .classed("selected-lasso", true)
+ .style("opacity", "1");
+
+ // Reset the style of the not selected dots
+ lasso.items().filter(function(d) {return d.selected === false;})
+ .classed("not-possible-lasso possible-lasso", false)
+ .classed("not-selected-lasso", true)
+ .style("opacity", function(d) { return settings.point_opacity / 7; });
+
+ // Call custom callback function
+ var callback_sel = svg.selectAll(".dot, .arrow").filter(function(d) {return d.selected === true;});
+ if (typeof settings.lasso_callback === 'function') settings.lasso_callback(callback_sel);
+ }
+ else {
+ lasso.items()
+ .classed("not-possible-lasso possible-lasso not-selected-lasso selected-lasso", false)
+ .style("opacity", function(d) {
+ if (d3.select(this).classed('point-label')) {return 1;};
+ return d.opacity_var !== undefined ? scales.opacity(d.opacity_var) : settings.point_opacity;
+ });
+ }
+};
+
+
+// Toggle lasso on / zoom off
+function lasso_on(svg, settings, scales, zoom) {
+ var root = svg.select(".root");
+ var chart_body = svg.select(".chart-body");
+
+ var lasso_classes = ".dot, .arrow, .point-label";
+ // Disable zoom behavior
+ root.on(".zoom", null);
+ // Enable lasso
+ var lasso = d3.lasso()
+ .closePathDistance(2000) // max distance for the lasso loop to be closed
+ .closePathSelect(true) // can items be selected by closing the path?
+ .hoverSelect(true) // can items by selected by hovering over them?
+ .area(root)
+ .items(chart_body.selectAll(lasso_classes))
+ .on("start", function() { lasso_start(lasso); }) // lasso start function
+ .on("draw", function() { lasso_draw(lasso); }) // lasso draw function
+ .on("end", function() { lasso_end(lasso, svg, settings, scales, zoom); }); // lasso end function
+ root.call(lasso);
+
+ // Change cursor style
+ root.style("cursor", "crosshair");
+ // Change togglers state
+ var menu_entry = d3.select("#scatterD3-menu-" + settings.html_id + " .lasso-entry");
+ var custom_entry = d3.select("#" + settings.dom_id_lasso_toggle);
+ if (!menu_entry.empty()) {
+ menu_entry.classed("active", true)
+ .html("Toggle lasso off");
+ }
+ if (!custom_entry.empty()) { custom_entry.classed("active", true); }
+}
+
+// Toggle lasso off / zoom on
+function lasso_off(svg, settings, zoom) {
+ var root = svg.select(".root");
+ // Disable lasso
+ root.on(".dragstart", null);
+ root.on(".drag", null);
+ root.on(".dragend", null);
+ // Enable zoom
+ root.call(zoom);
+ // Change cursor style
+ root.style("cursor", "move");
+ // Change togglers state
+ var menu_entry = d3.select("#scatterD3-menu-" + settings.html_id + " .lasso-entry");
+ var custom_entry = d3.select("#" + settings.dom_id_lasso_toggle);
+ if (!menu_entry.empty()) {
+ menu_entry.classed("active", false)
+ .html("Toggle lasso on");
+ }
+ if (!custom_entry.empty()) { custom_entry.classed("active", false); }
+}
+
+// Toggle lasso state when element clicked
+function lasso_toggle(svg, settings, scales, zoom) {
+ var menu_entry = d3.select("#scatterD3-menu-" + settings.html_id + " .lasso-entry");
+ var custom_entry = d3.select("#" + settings.dom_id_lasso_toggle);
+ if (settings.lasso &&
+ ((!menu_entry.empty() && menu_entry.classed("active")) ||
+ (!custom_entry.empty() && custom_entry.classed("active")))) {
+ lasso_off(svg, settings, zoom);
+ }
+ else {
+ lasso_on(svg, settings, scales, zoom);
+ }
+}
diff --git a/inst/htmlwidgets/scatterD3-legend.js b/inst/htmlwidgets/scatterD3-legend.js
new file mode 100644
index 0000000..1bceaa7
--- /dev/null
+++ b/inst/htmlwidgets/scatterD3-legend.js
@@ -0,0 +1,227 @@
+// Format legend label
+function legend_label_formatting (selection) {
+ selection
+ .style("text-anchor", "beginning")
+ .style("fill", "#000")
+ .style("font-weight", "bold");
+}
+
+// Create color legend
+function add_color_legend(svg, dims, settings, scales, duration) {
+
+ // Default transition duration to 0
+ duration = typeof duration !== 'undefined' ? duration : 0;
+
+ var legend = svg.select(".legend");
+ var legend_color_scale = scales.color.copy();
+ if (!settings.col_continuous) {
+ // Sort legend
+ legend_color_scale
+ .domain(legend_color_scale.domain().sort())
+ .range(legend_color_scale.domain().map(function(d) {return scales.color(d);}));
+ }
+
+ var color_legend = d3.legendColor()
+ .shapePadding(3)
+ .shape("rect")
+ .scale(legend_color_scale);
+
+ if (!settings.col_continuous) {
+ color_legend
+ .on("cellover", function(d) {
+ d = css_clean(d);
+ var nsel = ".color:not(.color-c" + d + "):not(.selected-lasso):not(.not-selected-lasso)";
+ var sel = ".color-c" + d + ":not(.selected-lasso):not(.not-selected-lasso)";
+ svg.selectAll(nsel)
+ .transition()
+ .style("opacity", 0.2);
+ svg.selectAll(sel)
+ .transition()
+ .style("opacity", 1);
+ })
+ .on("cellout", function(d) {
+ var sel = ".color:not(.selected-lasso):not(.not-selected-lasso)";
+ svg.selectAll(sel)
+ .transition()
+ .style("opacity", function(d2) {
+ return(d2.opacity_var === undefined ? settings.point_opacity : scales.opacity(d2.opacity_var));
+ });
+ svg.selectAll(".point-label:not(.selected-lasso):not(.not-selected-lasso)")
+ .transition()
+ .style("opacity", 1);
+ });
+ } else {
+ color_legend.cells(6);
+ }
+
+ legend.append("g")
+ .attr("class", "color-legend-label")
+ .append("text")
+ .text(settings.col_lab)
+ .call(legend_label_formatting);
+
+ legend.append("g")
+ .attr("class", "color-legend")
+ .call(color_legend);
+
+ legend.call(function(legend) { move_color_legend(legend, dims, 0);});
+
+ if (duration != 0) {
+ legend.selectAll(".color-legend-label, .color-legend")
+ .style("opacity", 0)
+ .transition().duration(duration)
+ .style("opacity", 1);
+ }
+
+}
+
+// Create symbol legend
+function add_symbol_legend(svg, dims, settings, scales, duration) {
+
+ // Default transition duration to 0
+ duration = typeof duration !== 'undefined' ? duration : 0;
+
+ var legend = svg.select(".legend");
+ // Sort legend
+ var legend_symbol_scale = scales.symbol.copy();
+ legend_symbol_scale
+ .domain(legend_symbol_scale.domain().sort())
+ .range(legend_symbol_scale.domain().map(function(d) {return d3.symbol().type(d3.symbols[scales.symbol(d)])();}));
+
+ var symbol_legend = d3.legendSymbol()
+ .shapePadding(5)
+ .scale(legend_symbol_scale)
+ .on("cellover", function(d) {
+ d = css_clean(d);
+ var nsel = ".symbol:not(.symbol-c" + d + "):not(.selected-lasso):not(.not-selected-lasso)";
+ var sel = ".symbol-c" + d + ":not(.selected-lasso):not(.not-selected-lasso)";
+ svg.selectAll(nsel)
+ .transition()
+ .style("opacity", 0.2);
+ svg.selectAll(sel)
+ .transition()
+ .style("opacity", 1);
+ })
+ .on("cellout", function(d) {
+ var sel = ".symbol:not(.selected-lasso):not(.not-selected-lasso)";
+ svg.selectAll(sel)
+ .transition()
+ .style("opacity", function(d2) {
+ return(d2.opacity_var === undefined ? settings.point_opacity : scales.opacity(d2.opacity_var));
+ });
+ svg.selectAll(".point-label:not(.selected-lasso):not(.not-selected-lasso)")
+ .transition()
+ .style("opacity", 1);
+ });
+
+ legend.append("g")
+ .append("text")
+ .attr("class", "symbol-legend-label")
+ .text(settings.symbol_lab)
+ .call(legend_label_formatting);
+
+ legend.append("g")
+ .attr("class", "symbol-legend")
+ .call(symbol_legend);
+
+ legend.call(function(sel) { move_symbol_legend(sel, dims, 0);});
+
+ if (duration != 0) {
+ legend.selectAll(".symbol-legend-label, .symbol-legend")
+ .style("opacity", 0)
+ .transition().duration(duration)
+ .style("opacity", 1);
+ }
+
+}
+
+// Create size legend
+function add_size_legend(svg, dims, settings, scales, duration) {
+
+ // Default transition duration to 0
+ duration = typeof duration !== 'undefined' ? duration : 0;
+
+ var legend = svg.select(".legend");
+ var legend_size_scale = scales.size.copy();
+ // FIXME : find exact formula
+ legend_size_scale.range(scales.size.range().map(function(d) {return Math.sqrt(d)/1.8;}));
+
+ var size_legend = d3.legendSize()
+ .shapePadding(3)
+ .shape('circle')
+ .scale(legend_size_scale);
+
+ legend.append("g")
+ .append("text")
+ .attr("class", "size-legend-label")
+ .text(settings.size_lab)
+ .call(legend_label_formatting);
+
+ legend.append("g")
+ .attr("class", "size-legend")
+ .call(size_legend);
+
+ legend.call(function(sel) { move_size_legend(sel, dims, 0);});
+
+ if (duration != 0) {
+ legend.selectAll(".size-legend-label, .size-legend")
+ .style("opacity", 0)
+ .transition().duration(duration)
+ .style("opacity", 1);
+ }
+
+
+}
+
+
+// Move color legend on resize
+function move_color_legend (legend, dims, duration) {
+ legend.select(".color-legend-label")
+ .transition().duration(duration)
+ .attr("transform", "translate(" + dims.legend_x + "," + dims.margins.legend_top + ")");
+ legend.select(".color-legend")
+ .transition().duration(duration)
+ .attr("transform", "translate(" + dims.legend_x + "," + (dims.margins.legend_top + 12) + ")");
+}
+
+// Move symbol legend on resize
+function move_symbol_legend (legend, dims, duration) {
+ legend.select(".symbol-legend-label")
+ .transition().duration(duration)
+ .attr("transform", "translate(" + dims.legend_x + "," + dims.margins.symbol_legend_top + ")");
+ legend.select(".symbol-legend")
+ .transition().duration(duration)
+ .attr("transform", "translate(" + (dims.legend_x + 8) + "," + (dims.margins.symbol_legend_top + 16) + ")");
+}
+
+// Move size legend on resize
+function move_size_legend (legend, dims, duration) {
+ legend.select(".size-legend-label")
+ .transition().duration(duration)
+ .attr("transform", "translate(" + dims.legend_x + "," + dims.margins.size_legend_top + ")");
+ legend.select(".size-legend")
+ .transition().duration(duration)
+ .attr("transform", "translate(" + (dims.legend_x + 8) + "," + (dims.margins.size_legend_top + 14) + ")");
+}
+
+
+// Remove color legend
+function remove_color_legend (legend) {
+ legend.selectAll(".color-legend-label, .color-legend")
+ .style("opacity", "0")
+ .remove();
+}
+
+// Remove symbol legend
+function remove_symbol_legend (legend) {
+ legend.selectAll(".symbol-legend-label, .symbol-legend")
+ .style("opacity", "0")
+ .remove();
+}
+
+// Remove size legend
+function remove_size_legend (legend) {
+ legend.selectAll(".size-legend-label, .size-legend")
+ .style("opacity", "0")
+ .remove();
+}
diff --git a/inst/htmlwidgets/scatterD3-lines.js b/inst/htmlwidgets/scatterD3-lines.js
new file mode 100644
index 0000000..cc93133
--- /dev/null
+++ b/inst/htmlwidgets/scatterD3-lines.js
@@ -0,0 +1,50 @@
+// Zero horizontal and vertical lines
+var draw_line = d3.line()
+ .x(function(d) {return d.x;})
+ .y(function(d) {return d.y;});
+
+function line_init(selection) {
+ selection
+ .attr("class", "line");
+
+ return selection;
+}
+
+function line_formatting(selection, dims, settings, scales) {
+ selection
+ .attr("d", function(d) {
+ // Categorical variables
+ if (settings.x_categorical && settings.y_categorical) { return null; };
+ if (settings.x_categorical) {
+ if (d.slope != 0) { return null; }
+ else {
+ return draw_line([{x:0, y: scales.y(d.intercept)},
+ {x:dims.width, y: scales.y(d.intercept)}]);
+ }
+ }
+ if (settings.y_categorical) {
+ if (d.slope !== null) { return null; }
+ }
+ // Vertical line
+ if (d.slope === null) {
+ return draw_line([{x:scales.x(d.intercept), y: 0},
+ {x:scales.x(d.intercept), y: dims.height}]);
+ }
+ // All other lines
+ else {
+ return draw_line([{x:0, y: scales.y(d.slope * scales.x.domain()[0] + d.intercept)},
+ {x:dims.width, y: scales.y(d.slope * scales.x.domain()[1] + d.intercept)}]);
+ }
+ })
+ .style("stroke-width", function(d) {
+ return d.stroke_width !== undefined && d.stroke_width !== null ? d.stroke_width : "1px";
+ })
+ .style("stroke", function(d) {
+ return d.stroke !== undefined && d.stroke !== null ? d.stroke : "#000000";
+ })
+ .style("stroke-dasharray", function(d) {
+ return d.stroke_dasharray !== undefined && d.stroke_dasharray !== null ? d.stroke_dasharray : null;
+ });
+
+ return selection;
+}
diff --git a/inst/htmlwidgets/scatterD3-setup.js b/inst/htmlwidgets/scatterD3-setup.js
new file mode 100644
index 0000000..7d6bf51
--- /dev/null
+++ b/inst/htmlwidgets/scatterD3-setup.js
@@ -0,0 +1,192 @@
+// Custom color scheme
+function custom_scheme10 () {
+ // slice() to create a copy
+ var scheme = d3.schemeCategory10.slice();
+ // Switch orange and red
+ var tmp = scheme[3];
+ scheme[3] = scheme[1];
+ scheme[1] = tmp;
+ return scheme;
+}
+
+// Setup dimensions
+function setup_sizes (width, height, settings) {
+
+ var dims = {},
+ margins = {top: 5, right: 10, bottom: 20, left: 30};
+
+ if (settings.left_margin !== null) {
+ margins.left = settings.left_margin;
+ }
+
+ dims.svg_width = width;
+ dims.svg_height = height;
+
+ dims.legend_width = 0;
+ if (settings.has_legend) dims.legend_width = settings.legend_width;
+
+ dims.width = width - dims.legend_width;
+ dims.height = height;
+ dims.height = dims.height - margins.top - margins.bottom;
+ dims.width = dims.width - margins.left - margins.right;
+
+ // Fixed ratio
+ if (settings.fixed) {
+ dims.height = Math.min(dims.height, dims.width);
+ dims.width = dims.height;
+ }
+
+ dims.total_width = dims.width + margins.left + margins.right + dims.legend_width;
+ dims.total_height = dims.height + margins.top + margins.bottom;
+
+ dims.legend_x = dims.total_width - margins.right - dims.legend_width + 24;
+
+ dims.margins = margins;
+
+ return dims;
+}
+
+// Compute and setup legend positions
+function setup_legend_sizes (dims, scales, settings) {
+
+ dims.margins.legend_top = 50;
+
+ // Height of color legend
+ var color_legend_height = 0;
+ if (settings.has_color_var) {
+ var n = settings.col_continuous ? 6 : scales.color.domain().length;
+ color_legend_height = n * 20 + 30;
+ }
+ dims.margins.symbol_legend_top = color_legend_height + dims.margins.legend_top;
+
+ // Height of symbol legend
+ var symbol_legend_height = settings.has_symbol_var ? scales.symbol.domain().length * 20 + 30 : 0;
+ dims.margins.size_legend_top = color_legend_height + symbol_legend_height + dims.margins.legend_top;
+
+ return dims;
+}
+
+// Compute and setup scales
+function setup_scales (dims, settings, data) {
+
+ var min_x, min_y, max_x, max_y, gap_x, gap_y;
+ var scales = {};
+
+ // x and y limits
+ if (settings.xlim === null) {
+ min_x = d3.min(data, function(d) { return(d.x);} );
+ max_x = d3.max(data, function(d) { return(d.x);} );
+ gap_x = (max_x - min_x) * 0.2;
+ } else {
+ min_x = settings.xlim[0];
+ max_x = settings.xlim[1];
+ gap_x = 0;
+ }
+ if (settings.ylim === null) {
+ min_y = d3.min(data, function(d) { return(d.y);} );
+ max_y = d3.max(data, function(d) { return(d.y);} );
+ gap_y = (max_y - min_y) * 0.2;
+ } else {
+ min_y = settings.ylim[0];
+ max_y = settings.ylim[1];
+ gap_y = 0;
+ }
+
+ // Fixed ratio
+ if (settings.fixed && !(settings.xlim !== null && settings.ylim !== null)) {
+ if (settings.xlim === null && settings.ylim === null) {
+ min_x = min_y = Math.min(min_x, min_y);
+ max_x = max_y = Math.max(max_x, max_y);
+ gap_x = gap_y = Math.max(gap_x, gap_y);
+ }
+ if (settings.xlim !== null) {
+ min_y = min_x;
+ max_y = max_x;
+ gap_y = gap_x;
+ }
+ if (settings.ylim !== null) {
+ min_x = min_y;
+ max_x = max_y;
+ gap_x = gap_y;
+ }
+ }
+
+ // x, y scales
+ if (!settings.x_categorical) {
+ scales.x = d3.scaleLinear()
+ .range([0, dims.width])
+ .domain([min_x - gap_x, max_x + gap_x]);
+ } else {
+ scales.x = d3.scalePoint()
+ .range([0, dims.width])
+ .padding(0.9)
+ .domain(d3.map(data, function(d){ return d.x; }).keys().sort());
+ }
+ if (!settings.y_categorical) {
+ scales.y = d3.scaleLinear()
+ .range([dims.height, 0])
+ .domain([min_y - gap_y, max_y + gap_y]);
+ } else {
+ scales.y = d3.scalePoint()
+ .range([dims.height, 0])
+ .padding(0.9)
+ .domain(d3.map(data, function(d){ return d.y; }).keys().sort());
+ }
+ // Keep track of original scales
+ scales.x_orig = scales.x;
+ scales.y_orig = scales.y;
+ // x and y axis functions
+ scales.xAxis = d3.axisBottom(scales.x)
+ .tickSize(-dims.height);
+ scales.yAxis = d3.axisLeft(scales.y)
+ .tickSize(-dims.width);
+
+ // Continuous color scale
+ if (settings.col_continuous) {
+ scales.color = d3.scaleSequential(d3.interpolateViridis)
+ .domain([d3.min(data, function(d) { return(d.col_var);} ),
+ d3.max(data, function(d) { return(d.col_var);} )]);
+ }
+ // Ordinal color scale
+ else {
+ if (settings.colors === null) {
+ // Number of different levels. See https://github.com/mbostock/d3/issues/472
+ var n = d3.map(data, function(d) { return d.col_var; }).size();
+ scales.color = n <= 9 ? d3.scaleOrdinal(custom_scheme10()) : d3.scaleOrdinal(d3.schemeCategory20);
+ } else if (Array.isArray(settings.colors)) {
+ scales.color = d3.scaleOrdinal().range(settings.colors);
+ } else if (typeof(settings.colors) === "string"){
+ // Single string given
+ scales.color = d3.scaleOrdinal().range(Array(settings.colors));
+ } else if (typeof(settings.colors) === "object"){
+ scales.color = d3.scaleOrdinal()
+ .range(d3.values(settings.colors))
+ .domain(d3.keys(settings.colors));
+ }
+ }
+ // Symbol scale
+ scales.symbol = d3.scaleOrdinal().range(d3.range(d3.symbols.length));
+ // Size scale
+ scales.size = d3.scaleLinear()
+ .range(settings.size_range)
+ .domain([d3.min(data, function(d) { return(d.size_var);} ),
+ d3.max(data, function(d) { return(d.size_var);} )]);
+ // Opacity scale
+ scales.opacity = d3.scaleLinear()
+ .range([0.1, 1])
+ .domain([d3.min(data, function(d) { return(d.opacity_var);} ),
+ d3.max(data, function(d) { return(d.opacity_var);} )]);
+
+ return scales;
+
+}
+
+
+
+
+// Gear icon path
+function gear_path () {
+ return "m 24.28,7.2087374 -1.307796,0 c -0.17052,-0.655338 -0.433486,-1.286349 -0.772208,-1.858846 l 0.927566,-0.929797 c 0.281273,-0.281188 0.281273,-0.738139 0,-1.019312 L 21.600185,1.8728727 C 21.319088,1.591685 20.863146,1.5914219 20.582048,1.8726096 L 19.650069,2.8001358 C 19.077606,2.4614173 18.446602,2.1982296 17.791262,2.0278389 l 0,-1.30783846 c 0,-0.39762 -0.313645,-0.72 -0.711262,-0.72 l -2.16,0 c -0.397618,0 -0.711262,0.32238 -0.711262,0.72 l 0,1.30783846 c -0.65534,0.170 [...]
+}
+
+
diff --git a/inst/htmlwidgets/scatterD3-utils.js b/inst/htmlwidgets/scatterD3-utils.js
new file mode 100644
index 0000000..a1798ff
--- /dev/null
+++ b/inst/htmlwidgets/scatterD3-utils.js
@@ -0,0 +1,33 @@
+// Clean variables levels to be valid CSS classes
+function css_clean(s) {
+ if (s === undefined) return "";
+ return s.toString().replace(/[^\w-]/g, "_");
+}
+
+// Default translation function for points and labels
+function translation(d, scales) {
+ return "translate(" + scales.x(d.x) + "," + scales.y(d.y) + ")";
+}
+
+// Create tooltip content function
+function tooltip_content(d, settings) {
+ // no tooltips
+ if (!settings.has_tooltips) return null;
+ if (settings.has_custom_tooltips) {
+ // custom tooltipsl
+ return d.tooltip_text;
+ } else {
+ // default tooltips
+ var text = Array();
+ if (settings.has_labels) text.push("<b>"+d.lab+"</b>");
+ var x_value = settings.x_categorical ? d.x : d.x.toFixed(3);
+ var y_value = settings.y_categorical ? d.y : d.y.toFixed(3);
+ text.push("<b>"+settings.xlab+":</b> "+ x_value);
+ text.push("<b>"+settings.ylab+":</b> "+ y_value);
+ if (settings.has_color_var) text.push("<b>"+settings.col_lab+":</b> "+d.col_var);
+ if (settings.has_symbol_var) text.push("<b>"+settings.symbol_lab+":</b> "+d.symbol_var);
+ if (settings.has_size_var) text.push("<b>"+settings.size_lab+":</b> "+d.size_var);
+ if (settings.has_opacity_var) text.push("<b>"+settings.opacity_lab+":</b> "+d.opacity_var);
+ return text.join("<br />");
+ }
+}
diff --git a/inst/htmlwidgets/scatterD3.css b/inst/htmlwidgets/scatterD3.css
new file mode 100644
index 0000000..ffe99f5
--- /dev/null
+++ b/inst/htmlwidgets/scatterD3.css
@@ -0,0 +1,99 @@
+.scatterD3-tooltip {
+ position: absolute;
+ color: #222;
+ background: #fff;
+ padding: .5em;
+ text-shadow: #f5f5f5 0 1px 0;
+ border-radius: 2px;
+ box-shadow: 0px 0px 7px 1px #a6a6a6;
+ opacity: 0.95;
+ font-family: Open Sans, Droid Sans, Helvetica, Verdana, sans-serif;
+ font-size: 10px;
+ z-index: 10;
+}
+
+.scatterD3-menu {
+ position: absolute;
+ opacity: 0;
+ width: 0;
+ overflow: hidden;
+ top: 37px;
+ right: 15px;
+ z-index: 10;
+ background: #F5F7F9;
+ box-shadow: 0px 0px 2px 1px #d6d6d6;
+ font-family: Open Sans, Droid Sans, Helvetica, Verdana, sans-serif;
+ font-size: 11px;
+ text-align: right;
+ list-style: none;
+ padding: 0.2em;
+ margin: 0;
+}
+
+.scatterD3-menu li {
+ white-space: nowrap;
+ padding: 0.6em 0.8em;
+ margin: 0;
+}
+
+.scatterD3-menu li a {
+ cursor: pointer;
+ color: #727476;
+ /* font-weight: 600; */
+ text-decoration: none;
+ text-transform: uppercase;
+}
+
+.scatterD3-menu li a:hover {
+ color: #25282A;
+}
+
+
+
+.hidden {
+ display: none;
+}
+
+.scatterD3 .dot {
+ cursor: default;
+}
+
+.scatterD3 .point-label {
+ cursor: pointer;
+}
+
+.scatterD3, .scatterD3:focus {
+ outline: 0px solid transparent
+}
+
+.scatterD3 .gear-menu {
+ cursor: pointer;
+ height: 25px;
+ width: 25px;
+}
+
+.scatterD3 .gear-menu path {
+ opacity: 0.2;
+ transition: opacity .3s;
+}
+
+.scatterD3 .gear-menu:hover path,
+.scatterD3 .gear-menu.selected path {
+ opacity: 0.8;
+ transition: opacity .3s;
+}
+
+.scatterD3 .root {
+ cursor: move;
+}
+
+/* Specific styles for shiny apps */
+
+.shiny-bound-output .label {
+ font-size: 100%;
+ font-weight: normal;
+ text-align: left;
+}
+
+
+
diff --git a/inst/htmlwidgets/scatterD3.js b/inst/htmlwidgets/scatterD3.js
index c737728..fc42e35 100644
--- a/inst/htmlwidgets/scatterD3.js
+++ b/inst/htmlwidgets/scatterD3.js
@@ -1,825 +1,284 @@
function scatterD3() {
var width = 600, // default width
- height = 600, // default height
- dims = {},
- margin = {top: 5, right: 10, bottom: 20, left: 50, legend_top: 50},
- settings = {},
- data = [],
- x, y, color_scale, symbol_scale, size_scale,
- min_x, min_y, max_x, max_y, gap_x, gap_y,
- xAxis, yAxis,
- svg,
- zeroline, zoom, drag,
- lasso_base, lasso_classes;
-
- function setup_sizes() {
-
- dims.legend_width = 0;
- if (settings.has_legend) dims.legend_width = settings.legend_width;
-
- dims.width = width - dims.legend_width;
- dims.height = height;
- dims.height = dims.height - margin.top - margin.bottom;
- dims.width = dims.width - margin.left - margin.right;
-
- // Fixed ratio
- if (settings.fixed) {
- dims.height = Math.min(dims.height, dims.width);
- dims.width = dims.height;
- }
-
- dims.total_width = dims.width + margin.left + margin.right + dims.legend_width;
- dims.total_height = dims.height + margin.top + margin.bottom;
-
- dims.legend_x = dims.total_width - margin.right - dims.legend_width + 24;
- }
-
- function setup_scales() {
-
- // x and y limits
- if (settings.xlim === null) {
- min_x = d3.min(data, function(d) { return(d.x);} );
- max_x = d3.max(data, function(d) { return(d.x);} );
- gap_x = (max_x - min_x) * 0.2;
- } else {
- min_x = settings.xlim[0];
- max_x = settings.xlim[1];
- gap_x = 0;
- }
- if (settings.ylim === null) {
- min_y = d3.min(data, function(d) { return(d.y);} );
- max_y = d3.max(data, function(d) { return(d.y);} );
- gap_y = (max_y - min_y) * 0.2;
- } else {
- min_y = settings.ylim[0];
- max_y = settings.ylim[1];
- gap_y = 0;
- }
-
- // Fixed ratio
- if (settings.fixed) {
- if (settings.xlim === null && settings.ylim === null) {
- min_x = min_y = Math.min(min_x, min_y);
- max_x = max_y = Math.max(max_x, max_y);
- gap_x = gap_y = Math.max(gap_x, gap_y);
- }
- if (settings.xlim !== null) {
- min_y = min_x;
- max_y = max_x;
- gap_y = gap_x;
- }
- if (settings.ylim !== null) {
- min_x = min_y;
- max_x = max_y;
- gap_x = gap_y;
- }
-
- }
-
- // x, y, color, symbol and size scales
- x = d3.scale.linear().range([0, dims.width]);
- y = d3.scale.linear().range([dims.height, 0]);
- x.domain([min_x - gap_x, max_x + gap_x]);
- y.domain([min_y - gap_y, max_y + gap_y]);
- if (settings.colors === null) {
- // Number of different levels. See https://github.com/mbostock/d3/issues/472
- var n = d3.map(data, function(d) { return d.col_var; }).size();
- color_scale = n <= 10 ? d3.scale.category10() : d3.scale.category20();
- } else if (Array.isArray(settings.colors)) {
- color_scale = d3.scale.ordinal().range(settings.colors);
- } else if (typeof(settings.colors) === "object"){
- color_scale = d3.scale.ordinal()
- .range(d3.values(settings.colors))
- .domain(d3.keys(settings.colors));
- }
- symbol_scale = d3.scale.ordinal().range(d3.range(d3.svg.symbolTypes.length));
- size_scale = d3.scale.linear()
- .range(settings.size_range)
- .domain([d3.min(data, function(d) { return(d.size_var);} ),
- d3.max(data, function(d) { return(d.size_var);} )]);
-
- // zoom behavior
- zoom = d3.behavior.zoom()
- .x(x)
- .y(y)
+ height = 600, // default height
+ dims = {},
+ settings = {},
+ scales = {},
+ data = [],
+ svg,
+ zoom, drag;
+
+ // Zoom behavior
+ zoom = d3.zoom()
.scaleExtent([0, 32])
.on("zoom", zoomed);
-
- // x and y axis functions
- xAxis = d3.svg.axis()
- .scale(x)
- .orient("bottom")
- .tickSize(-dims.height);
- yAxis = d3.svg.axis()
- .scale(y)
- .orient("left")
- .tickSize(-dims.width);
-
- }
-
- // Key function to identify rows when interactively filtering
- function key(d) {
- return d.key_var;
- }
-
- // Default translation function for points and labels
- function translation(d) {
- return "translate(" + x(d.x) + "," + y(d.y) + ")";
- }
-
+
// Zoom function
function zoomed(reset) {
- svg.select(".x.axis").call(xAxis);
- svg.select(".y.axis").call(yAxis);
- svg.selectAll(".dot, .point-label")
- .attr("transform", translation);
- svg.selectAll(".arrow").call(draw_arrow);
- svg.selectAll(".ellipse").call(ellipse_formatting);
- var zeroline = d3.svg.line()
- .x(function(d) {return x(d.x)})
- .y(function(d) {return y(d.y)});
- svg.select(".zeroline.hline").attr("d", zeroline([{x:x.domain()[0], y:0}, {x:x.domain()[1], y:0}]));
- svg.select(".zeroline.vline").attr("d", zeroline([{x:0, y:y.domain()[0]}, {x:0, y:y.domain()[1]}]));
- svg.select(".unit-circle").call(unit_circle_init);
-
- }
-
- // Create and draw x and y axes
- function add_axes(selection) {
-
- // x axis
- selection.append("g")
- .attr("class", "x axis")
- .attr("transform", "translate(0," + dims.height + ")")
- .call(xAxis)
- .append("text")
- .attr("class", "axis-label")
- .attr("x", dims.width - 5)
- .attr("y", -6)
- .style("text-anchor", "end")
- .text(settings.xlab);
-
- // y axis
- selection.append("g")
- .attr("class", "y axis")
- .call(yAxis)
- .append("text")
- .attr("class", "axis-label")
- .attr("transform", "rotate(-90)")
- .attr("x", -5)
- .attr("y", 6)
- .attr("dy", ".71em")
- .style("text-anchor", "end")
- .text(settings.ylab);
-
- }
-
- // Zero horizontal and vertical lines
- zeroline = d3.svg.line()
- .x(function(d) {return x(d.x)})
- .y(function(d) {return y(d.y)});
-
- // Create tooltip content function
- function tooltip_content(d) {
- // no tooltips
- if (!settings.has_tooltips) return null;
- if (settings.has_custom_tooltips) {
- // custom tooltips
- return d.tooltip_text;
- } else {
- // default tooltips
- var text = Array();
- if (settings.has_labels) text.push("<b>"+d.lab+"</b>");
- text.push("<b>"+settings.xlab+":</b> "+d.x.toFixed(3));
- text.push("<b>"+settings.ylab+":</b> "+d.y.toFixed(3));
- if (settings.has_color_var) text.push("<b>"+settings.col_lab+":</b> "+d.col_var);
- if (settings.has_symbol_var) text.push("<b>"+settings.symbol_lab+":</b> "+d.symbol_var);
- if (settings.has_size_var) text.push("<b>"+settings.size_lab+":</b> "+d.size_var);
- return text.join("<br />");
- }
+ var root = svg.select(".root");
+ if (!settings.x_categorical) {
+ scales.x = d3.event.transform.rescaleX(scales.x_orig);
+ scales.xAxis = scales.xAxis.scale(scales.x);
+ root.select(".x.axis").call(scales.xAxis);
+ }
+ if (!settings.y_categorical) {
+ scales.y = d3.event.transform.rescaleY(scales.y_orig);
+ scales.yAxis = scales.yAxis.scale(scales.y);
+ root.select(".y.axis").call(scales.yAxis);
+ }
+ var chart_body = svg.select(".chart-body");
+ chart_body.selectAll(".dot, .point-label")
+ .attr("transform", function(d) { return translation(d, scales); });
+ chart_body.selectAll(".line").call(function(sel) {
+ line_formatting(sel, dims, settings, scales);
+ });
+ chart_body.selectAll(".arrow").call(function(sel) { draw_arrow(sel, scales);});
+ chart_body.selectAll(".ellipse").call(function(sel) { ellipse_formatting(sel, settings, scales);});
+ svg.select(".unit-circle").call(function(sel) { add_unit_circle(sel, scales); });
+ if (typeof settings.zoom_callback === 'function') {
+ settings.zoom_callback(scales.x.domain()[0], scales.x.domain()[1], scales.y.domain()[0], scales.y.domain()[1]);
+ }
+ }
+
+ // Reset zoom function
+ function reset_zoom() {
+ var root = svg.select(".root");
+ root.transition().duration(750).call(zoom.transform, d3.zoomIdentity);
}
- // Clean variables levels to be valid CSS classes
- function css_clean(s) {
- if (s === undefined) return "";
- return s.toString().replace(/[^\w-]/g, "_");
- }
-
- // Initial dot attributes
- function dot_init (selection) {
- // tooltips when hovering points
- if (settings.has_tooltips) {
- var tooltip = d3.select(".scatterD3-tooltip");
- selection.on("mouseover", function(d, i){
- tooltip.style("visibility", "visible")
- .html(tooltip_content(d));
- });
- selection.on("mousemove", function(){
- tooltip.style("top", (d3.event.pageY+15)+"px").style("left",(d3.event.pageX+15)+"px");
- });
- selection.on("mouseout", function(){
- tooltip.style("visibility", "hidden");
- });
- }
- }
-
- // Apply format to dot
- function dot_formatting(selection) {
- var sel = selection
- .attr("transform", translation)
- // fill color
- .style("fill", function(d) { return color_scale(d.col_var); })
- // symbol and size
- .attr("d", d3.svg.symbol()
- .type(function(d) {return d3.svg.symbolTypes[symbol_scale(d.symbol_var)]})
- .size(function(d) {
- if (settings.has_size_var) { return size_scale(d.size_var)}
- else { return settings.point_size }
- })
- )
- .attr("class", function(d,i) {
- return "dot symbol symbol-c" + css_clean(d.symbol_var) + " color color-c" + css_clean(d.col_var);
- })
- if (settings.opacity_changed || settings.subset_changed || settings.redraw) {
- sel = sel
- .style("opacity", function(d) {return d.point_opacity});
- }
- return sel;
- }
-
- // Arrow drawing function
- function draw_arrow(selection) {
- selection
- .attr("x1", function(d) { return x(0) })
- .attr("y1", function(d) { return y(0) })
- .attr("x2", function(d) { return x(d.x) })
- .attr("y2", function(d) { return y(d.y) });
- }
-
- // Initial arrow attributes
- function arrow_init (selection) {
- selection
- // tooltips when hovering points
- if (settings.has_tooltips) {
- var tooltip = d3.select(".scatterD3-tooltip");
- selection.on("mouseover", function(d, i){
- tooltip.style("visibility", "visible")
- .html(tooltip_content(d));
- });
- selection.on("mousemove", function(){
- tooltip.style("top", (d3.event.pageY+15)+"px").style("left",(d3.event.pageX+15)+"px");
- });
- selection.on("mouseout", function(){
- tooltip.style("visibility", "hidden");
- });
- }
- }
-
- // Apply format to arrow
- function arrow_formatting(selection) {
- var sel = selection
- .call(draw_arrow)
- .style("stroke-width", "1px")
- // stroke color
- .style("stroke", function(d) { return color_scale(d.col_var); })
- .attr("marker-end", function(d) { return "url(#arrow-head-" + settings.html_id + "-" + color_scale(d.col_var) + ")" })
- .attr("class", function(d,i) { return "arrow color color-c" + css_clean(d.col_var) });
- if (settings.opacity_changed || settings.subset_changed || settings.redraw) {
- sel = sel.style("opacity", function(d) {return d.point_opacity});
- }
- return sel;
- }
-
- // Initial ellipse attributes
- function ellipse_init(selection) {
- selection
- .style("fill", "none");
- }
-
- // Apply format to ellipse
- function ellipse_formatting(selection) {
-
- // Ellipses path function
- var ellipseFunc = d3.svg.line()
- .x(function(d) { return x(d.x); })
- .y(function(d) { return y(d.y); });
-
- selection
- .attr("d", function(d) {
- var ell = HTMLWidgets.dataframeToD3(d.data);
- return (ellipseFunc(ell))
- })
- .style("stroke", function(d) {
- // Only one ellipse
- if (d.level == "_scatterD3_all") {
- return(color_scale.range()[0]);
- }
- return( color_scale(d.level))
- })
- .style("opacity", 1)
- .attr("class", function(d) {
- return "ellipse color color-c" + css_clean(d.level);
- });
- }
-
- // Unit circle init
- function unit_circle_init(selection) {
- selection
- .attr('cx', x(0))
- .attr('cy', y(0))
- .attr('rx', x(1)-x(0))
- .attr('ry', y(0)-y(1))
- .style("stroke", "#888")
- .style("fill", "none")
- .style("opacity", "1");
- }
-
- // Initial text label attributes
- function label_init (selection) {
- selection
- .attr("text-anchor", "middle");
- }
-
- // Compute default vertical offset for labels
- function default_label_dy(size, y, type_var) {
- if (y < 0 && type_var !== undefined && type_var == "arrow") {
- return (Math.sqrt(size) / 2) + settings.labels_size + 2;
- }
- else {
- return (-Math.sqrt(size) / 2) - 6;
- }
- }
-
- // Apply format to text label
- function label_formatting (selection) {
- var sel = selection
- .text(function(d) {return(d.lab)})
- .style("font-size", settings.labels_size + "px")
- .attr("class", function(d,i) { return "point-label color color-c" + css_clean(d.col_var) + " symbol symbol-c" + css_clean(d.symbol_var); })
- .attr("transform", translation)
- .style("fill", function(d) { return color_scale(d.col_var); })
- .attr("dx", function(d) {
- if (d.lab_dx === undefined) return("0px");
- else return(d.lab_dx + "px");
- })
- .attr("dy", function(d) {
- if (d.lab_dy !== undefined) return(d.lab_dy + "px");
- var size = (d.size_var === undefined) ? settings.point_size : size_scale(d.size_var);
- return default_label_dy(size, d.y, d.type_var) + "px";
- });
- if (settings.opacity_changed || settings.subset_changed || settings.redraw) {
- sel = sel.style("opacity", 1);
- }
- return sel;
- }
// Text labels dragging function
var dragging = false;
- drag = d3.behavior.drag()
- .origin(function(d) {
- var size = (d.size_var === undefined) ? settings.point_size : size_scale(d.size_var);
- var dx = (d.lab_dx === undefined) ? 0 : d.lab_dx;
- var dy = (d.lab_dx === undefined) ? default_label_dy(size, d.y, d.type_var) : d.lab_dy;
- return {x:x(d.x)+dx, y:y(d.y)+dy};
- })
- .on('dragstart', function(d) {
- if (!d3.event.sourceEvent.shiftKey) {
- dragging = true;
- d3.select(this).style('fill', '#000');
- var chart = d3.select(this).node().parentNode;
- var size = (d.size_var === undefined) ? settings.point_size : size_scale(d.size_var);
- var dx = (d.lab_dx === undefined) ? 0 : d.lab_dx;
- var dy = (d.lab_dx === undefined) ? default_label_dy(size, d.y, d.type_var) : d.lab_dy;
- d3.select(chart).append("svg:line")
- .attr("id", "scatterD3-drag-line")
- .attr("x1", x(d.x)).attr("x2", x(d.x) + dx)
- .attr("y1", y(d.y)).attr("y2", y(d.y) + dy)
- .style("stroke", "#000")
- .style("opacity", 0.3);
- }
- })
- .on('drag', function(d) {
- if (dragging) {
- cx = d3.event.x - x(d.x);
- cy = d3.event.y - y(d.y);
- d3.select(this)
- .attr('dx', cx + "px")
- .attr('dy', cy + "px");
- d3.select("#scatterD3-drag-line")
- .attr('x2', x(d.x) + cx)
- .attr("y2", y(d.y) + cy);
- d.lab_dx = cx;
- d.lab_dy = cy;
- }
- })
- .on('dragend', function(d) {
- if (dragging){
- d3.select(this).style('fill', color_scale(d.col_var));
- d3.select("#scatterD3-drag-line").remove();
- dragging = false;
- }
- });
-
-
-
- // Lasso functions to execute while lassoing
- lasso_start = function() {
- lasso.items()
- .each(function(d){
- if (d3.select(this).classed('dot')) {
- d.scatterD3_lasso_dot_stroke = d.scatterD3_lasso_dot_stroke ? d.scatterD3_lasso_dot_stroke : d3.select(this).style("stroke");
- d.scatterD3_lasso_dot_fill = d.scatterD3_lasso_dot_fill ? d.scatterD3_lasso_dot_fill : d3.select(this).style("fill");
- d.scatterD3_lasso_dot_opacity = d.scatterD3_lasso_dot_opacity ? d.scatterD3_lasso_dot_opacity : d3.select(this).style("opacity");
- }
- if (d3.select(this).classed('arrow')) {
- d.scatterD3_lasso_arrow_stroke = d.scatterD3_lasso_arrow_stroke ? d.scatterD3_lasso_arrow_stroke : d3.select(this).style("stroke");
- d.scatterD3_lasso_arrow_fill = d.scatterD3_lasso_arrow_fill ? d.scatterD3_lasso_arrow_fill : d3.select(this).style("fill");
- d.scatterD3_lasso_arrow_opacity = d.scatterD3_lasso_arrow_opacity ? d.scatterD3_lasso_arrow_opacity : d3.select(this).style("opacity");
- }
- if (d3.select(this).classed('point-label')) {
- d.scatterD3_lasso_text_stroke = d.scatterD3_lasso_text_stroke ? d.scatterD3_lasso_text_stroke : d3.select(this).style("stroke");
- d.scatterD3_lasso_text_fill = d.scatterD3_lasso_text_fill ? d.scatterD3_lasso_text_fill : d3.select(this).style("fill");
- d.scatterD3_lasso_text_opacity = d.scatterD3_lasso_text_opacity ? d.scatterD3_lasso_text_opacity : d3.select(this).style("opacity");
- }
- })
- .style("fill", null) // clear all of the fills
- .style("opacity", null) // clear all of the opacities
- .style("stroke", null) // clear all of the strokes
- .classed({"not-possible-lasso": true, "selected-lasso": false, "not-selected-lasso": false}); // style as not possible
- };
- lasso_draw = function() {
- // Style the possible dots
- lasso.items().filter(function(d) {return d.possible === true})
- .classed({"not-possible-lasso": false, "possible-lasso": true});
- // Style the not possible dot
- lasso.items().filter(function(d) {return d.possible === false})
- .classed({"not-possible-lasso": true, "possible-lasso": false});
- };
- lasso_end = function() {
- lasso_off(svg);
- d3.select("#" + settings.dom_id_lasso_toggle).classed("active", false);
- var some_selected = false;
- if(lasso.items().filter(function(d) {return d.selected === true})[0].length !== 0){
- some_selected = true;
- }
- // Reset the color of all dots
- lasso.items()
- .style("fill", function(d) {
- if (d3.select(this).classed('point-label')) { return d.scatterD3_lasso_text_fill; }
- if (d3.select(this).classed('dot')) { return d.scatterD3_lasso_dot_fill; }
- if (d3.select(this).classed('arrow')) { return d.scatterD3_lasso_arrow_fill; }
- })
- .style("opacity", function(d) {
- if (d3.select(this).classed('point-label')) { return d.scatterD3_lasso_text_opacity; }
- if (d3.select(this).classed('dot')) { return d.scatterD3_lasso_dot_opacity; }
- if (d3.select(this).classed('arrow')) { return d.scatterD3_lasso_arrow_opacity; }
- })
- .style("stroke", function(d) {
- if (d3.select(this).classed('point-label')) { return d.scatterD3_lasso_text_stroke; }
- if (d3.select(this).classed('dot')) { return d.scatterD3_lasso_dot_stroke; }
- if (d3.select(this).classed('arrow')) { return d.scatterD3_lasso_arrow_stroke; }
- });
- if (some_selected) {
- // Style the selected dots
- var sel = lasso.items().filter(function(d) {return d.selected === true})
- .classed({"not-possible-lasso": false, "possible-lasso": false, "selected-lasso": true})
- .style("opacity", "1");
-
- // Reset the style of the not selected dots
- lasso.items().filter(function(d) {return d.selected === false})
- .classed({"not-possible-lasso": false, "possible-lasso": false, "not-selected-lasso": true})
- .style("opacity", function(d) { return d.point_opacity / 7 });
-
- // Call custom callback function
- var callback_sel = svg.selectAll(".dot, .arrow").filter(function(d) {return d.selected === true});
- if (typeof settings.lasso_callback === 'function') settings.lasso_callback(callback_sel);
- }
- else {
- lasso.items()
- .classed({"not-possible-lasso": false, "possible-lasso": false,
- "not-selected-lasso": false, "selected-lasso": false})
- .style("opacity", function(d) {
- if (d3.select(this).classed('point-label')) {return 1};
- return d.point_opacity;
- })
- }
- };
- lasso_classes = ".dot, .arrow, .point-label";
- // Define the lasso
- lasso_base = d3.lasso()
- .closePathDistance(2000) // max distance for the lasso loop to be closed
- .closePathSelect(true) // can items be selected by closing the path?
- .hoverSelect(true) // can items by selected by hovering over them?
- .on("start", lasso_start) // lasso start function
- .on("draw", lasso_draw) // lasso draw function
- .on("end", lasso_end); // lasso end function
-
- // Toggle lasso on / zoom off
- function lasso_on(svg) {
- var pane = svg.select(".pane");
- var chart_body = svg.select(".chart-body");
- // Disable zoom behavior
- pane.on(".zoom", null);
- // Enable lasso
- lasso = lasso_base
- .area(pane)
- .items(chart_body.selectAll(lasso_classes));
- chart_body.call(lasso);
- // Change cursor style
- pane.style("cursor", "crosshair");
- }
-
- // Toggle lasso off / zoom on
- function lasso_off(svg) {
- var pane = svg.select(".pane");
- // Disable lasso
- pane.on(".dragstart", null);
- pane.on(".drag", null);
- pane.on(".dragend", null);
- // Enable zoom
- pane.call(zoom);
- // Change cursor style
- pane.style("cursor", "move");
- }
-
- // Format legend label
- function legend_label_formatting (selection, margin_top) {
- selection
- .style("text-anchor", "beginning")
- .style("fill", "#000")
- .style("font-weight", "bold");
- }
-
- // Create color legend
- function add_color_legend() {
-
- var legend = svg.select(".legend");
-
- var legend_color_domain = color_scale.domain().sort();
- var legend_color_scale = d3.scale.category10();
-
- legend_color_scale
- .domain(legend_color_domain)
- .range(legend_color_domain.map(function(d) {return color_scale(d)}));
-
- var color_legend = d3.legend.color()
- .shapePadding(3)
- .shape("rect")
- .scale(legend_color_scale)
- .on("cellover", function(d) {
- d = css_clean(d);
- var nsel = ".color:not(.color-c" + d + "):not(.selected-lasso):not(.not-selected-lasso)";
- var sel = ".color-c" + d + ":not(.selected-lasso):not(.not-selected-lasso)";
- svg.selectAll(nsel)
- .transition()
- .style("opacity", 0.2);
- svg.selectAll(sel)
- .transition()
- .style("opacity", 1);
- })
- .on("cellout", function(d) {
- var sel = ".color:not(.selected-lasso):not(.not-selected-lasso)";
- svg.selectAll(sel)
- .transition()
- .style("opacity", function(d2) {return d2.point_opacity});
- svg.selectAll(".point-label:not(.selected-lasso):not(.not-selected-lasso)")
- .transition()
- .style("opacity", 1);
- });
-
- legend.append("g")
- .append("text")
- .attr("class", "color-legend-label")
- .attr("transform", "translate(" + dims.legend_x + "," + margin.legend_top + ")")
- .text(settings.col_lab)
- .call(legend_label_formatting);
-
- legend.append("g")
- .attr("class", "color-legend")
- .attr("transform", "translate(" + dims.legend_x + "," + (margin.legend_top + 8) + ")")
- .call(color_legend);
- }
-
- // Create symbol legend
- function add_symbol_legend() {
-
- var legend = svg.select(".legend");
-
- // Height of color legend
- var color_legend_height = settings.has_color_var ? color_scale.domain().length * 20 + 30 : 0;
- margin.symbol_legend_top = color_legend_height + margin.legend_top;
-
- var legend_symbol_domain = symbol_scale.domain().sort();
- var legend_symbol_scale = d3.scale.ordinal()
- .domain(legend_symbol_domain)
- .range(legend_symbol_domain.map(function(d) {return d3.svg.symbol().type(d3.svg.symbolTypes[symbol_scale(d)])()}));
-
- var symbol_legend = d3.legend.symbol()
- .shapePadding(5)
- .scale(legend_symbol_scale)
- .on("cellover", function(d) {
- d = css_clean(d);
- var nsel = ".symbol:not(.symbol-c" + d + "):not(.selected-lasso):not(.not-selected-lasso)";
- var sel = ".symbol-c" + d + ":not(.selected-lasso):not(.not-selected-lasso)";
- svg.selectAll(nsel)
- .transition()
- .style("opacity", 0.2);
- svg.selectAll(sel)
- .transition()
- .style("opacity", 1);
- })
- .on("cellout", function(d) {
- var sel = ".symbol:not(.selected-lasso):not(.not-selected-lasso)";
- svg.selectAll(sel)
- .transition()
- .style("opacity", function(d2) {return d2.point_opacity});
- svg.selectAll(".point-label:not(.selected-lasso):not(.not-selected-lasso)")
- .transition()
- .style("opacity", 1);
- });
-
- legend.append("g")
- .append("text")
- .attr("class", "symbol-legend-label")
- .attr("transform", "translate(" + dims.legend_x + "," + margin.symbol_legend_top + ")")
- .text(settings.symbol_lab)
- .call(legend_label_formatting);
-
- legend.append("g")
- .attr("class", "symbol-legend")
- .attr("transform", "translate(" + (dims.legend_x + 8) + "," + (margin.symbol_legend_top + 14) + ")")
- .call(symbol_legend);
-
- }
-
- // Create size legend
- function add_size_legend() {
-
- var legend = svg.select(".legend");
-
- // Height of color and symbol legends
- var color_legend_height = settings.has_color_var ? color_scale.domain().length * 20 + 30 : 0;
- var symbol_legend_height = settings.has_symbol_var ? symbol_scale.domain().length * 20 + 30 : 0;
- margin.size_legend_top = color_legend_height + symbol_legend_height + margin.legend_top;
-
- var legend_size_scale = d3.scale.linear()
- .domain(size_scale.domain())
- // FIXME : find exact formula
- .range(size_scale.range().map(function(d) {return Math.sqrt(d)/1.8}));
+ drag = d3.drag()
+ .subject(function(d) {
+ var size = (d.size_var === undefined) ? settings.point_size : scales.size(d.size_var);
+ var dx = (d.lab_dx === undefined) ? 0 : d.lab_dx;
+ var dy = (d.lab_dx === undefined) ? default_label_dy(size, d.y, d.type_var, settings) : d.lab_dy;
+ return {x:scales.x(d.x)+dx, y:scales.y(d.y)+dy};
+ })
+ .on('start', function(d) {
+ if (!d3.event.sourceEvent.shiftKey) {
+ dragging = true;
+ d3.select(this).style('fill', '#000');
+ var chart = d3.select(this).node().parentNode;
+ var size = (d.size_var === undefined) ? settings.point_size : scales.size(d.size_var);
+ var dx = (d.lab_dx === undefined) ? 0 : d.lab_dx;
+ var dy = (d.lab_dx === undefined) ? default_label_dy(size, d.y, d.type_var, settings) : d.lab_dy;
+ d3.select(chart).append("svg:line")
+ .attr("id", "scatterD3-drag-line")
+ .attr("x1", scales.x(d.x)).attr("x2", scales.x(d.x) + dx)
+ .attr("y1", scales.y(d.y)).attr("y2", scales.y(d.y) + dy)
+ .style("stroke", "#000")
+ .style("opacity", 0.3);
+ }
+ })
+ .on('drag', function(d) {
+ if (dragging) {
+ var cx = d3.event.x - scales.x(d.x);
+ var cy = d3.event.y - scales.y(d.y);
+ d3.select(this)
+ .attr('dx', cx + "px")
+ .attr('dy', cy + "px");
+ d3.select("#scatterD3-drag-line")
+ .attr('x2', scales.x(d.x) + cx)
+ .attr("y2", scales.y(d.y) + cy);
+ d.lab_dx = cx;
+ d.lab_dy = cy;
+ }
+ })
+ .on('end', function(d) {
+ if (dragging){
+ d3.select(this).style('fill', scales.color(d.col_var));
+ d3.select("#scatterD3-drag-line").remove();
+ dragging = false;
+ }
+ });
- var size_legend = d3.legend.size()
- .shapePadding(3)
- .shape('circle')
- .scale(legend_size_scale);
-
- legend.append("g")
- .append("text")
- .attr("class", "size-legend-label")
- .attr("transform", "translate(" + dims.legend_x + "," + margin.size_legend_top + ")")
- .text(settings.size_lab)
- .call(legend_label_formatting);
-
- legend.append("g")
- .attr("class", "size-legend")
- .attr("transform", "translate(" + (dims.legend_x + 8) + "," + (margin.size_legend_top + 14) + ")")
- .call(size_legend);
+ // Key function to identify rows when interactively filtering
+ function key(d) {
+ return d.key_var;
}
-
// Filter points and arrows data
function point_filter(d) {
- return d.type_var === undefined || d.type_var == "point";
+ return d.type_var === undefined || d.type_var == "point";
}
function arrow_filter(d) {
- return d.type_var !== undefined && d.type_var == "arrow";
+ return d.type_var !== undefined && d.type_var == "arrow";
}
function chart(selection) {
selection.each(function() {
- setup_sizes();
- setup_scales();
+ dims = setup_sizes(width, height, settings);
+ scales = setup_scales(dims, settings, data);
// Root chart element and axes
- root = svg.append("g")
- .attr("class", "root")
- .style("fill", "#FFF")
- .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
- .call(add_axes);
-
- // <defs>
- var defs = svg.append("defs");
- // clipping rectangle
- defs.append("clipPath")
- .attr('id', 'scatterclip-' + settings.html_id)
- .append('rect')
- .attr('class', 'cliprect')
- .attr('width', dims.width)
- .attr('height', dims.height);
- // arrow head markers
- color_scale.range().forEach(function(d) {
- defs.append("marker")
- .attr("id", "arrow-head-" + settings.html_id + "-" + d)
- .attr("markerWidth", "10")
- .attr("markerHeight", "10")
- .attr("refX", "10")
- .attr("refY", "4")
- .attr("orient", "auto")
- .append("path")
- .attr("d", "M0,0 L0,8 L10,4 L0,0")
- .style("fill", d);
- });
-
- // zoom pane
- var pane = root.append("rect")
- .attr("class", "pane")
- .attr("width", dims.width)
- .attr("height", dims.height)
- .style("fill", "none")
- .style("pointer-events", "all")
- .call(zoom);
+ var root = svg.append("g")
+ .attr("class", "root")
+ .attr("transform", "translate(" + dims.margins.left + "," + dims.margins.top + ")")
+ .call(zoom);
+
+ root.append("rect")
+ .style("fill", "#FFF")
+ .attr("width", dims.width)
+ .attr("height", dims.height);
+
+ root.call(function(sel) { add_axes(sel, dims, settings, scales); });
// chart body
- var clip_path_url = document.location.href.replace(/#[^?]+?(\?|$)/, "$1") +
- "#scatterclip-" + settings.html_id
- var chart_body = root.append("g")
- .attr("class", "chart-body")
- .attr("width", dims.width)
- .attr("height", dims.height)
- .attr("clip-path", "url(" + clip_path_url + ")");
+ var chart_body = root.append("svg")
+ .attr("class", "chart-body")
+ .attr("width", dims.width)
+ .attr("height", dims.height);
+
+ // lines
+ if (settings.lines !== null) {
+ var lines = chart_body
+ .selectAll(".lines")
+ .data(HTMLWidgets.dataframeToD3(settings.lines));
+ lines.enter()
+ .append("path")
+ .call(line_init)
+ .call(function(sel) {
+ line_formatting(sel, dims, settings, scales);
+ });
+ }
- chart_body.append("path")
- .attr("class", "zeroline hline")
- .attr("d", zeroline([{x:x.domain()[0], y:0}, {x:x.domain()[1], y:0}]));
- chart_body.append("path")
- .attr("class", "zeroline vline")
- .attr("d", zeroline([{x:0, y:y.domain()[0]}, {x:0, y:y.domain()[1]}]));
// Unit circle
if (settings.unit_circle) {
- var unit_circle = chart_body.append('svg:ellipse')
- .attr('class', 'unit-circle')
- .call(unit_circle_init);
+ var unit_circle = chart_body.append('svg:ellipse')
+ .attr('class', 'unit-circle')
+ .call(function(sel) { add_unit_circle(sel, scales); });
}
// Add points
var dot = chart_body
- .selectAll(".dot")
- .data(data.filter(point_filter), key);
+ .selectAll(".dot")
+ .data(data.filter(point_filter), key);
dot.enter()
- .append("path")
- .call(dot_init)
- .call(dot_formatting);
+ .append("path")
+ .call(function(sel) { dot_init(sel, settings, scales); })
+ .call(function(sel) { dot_formatting(sel, settings, scales); });
// Add arrows
+ if (!settings.col_continuous) add_arrows_defs(svg, settings, scales);
var arrow = chart_body
- .selectAll(".arrow")
- .data(data.filter(arrow_filter), key);
+ .selectAll(".arrow")
+ .data(data.filter(arrow_filter), key);
arrow.enter()
- .append("svg:line")
- .call(arrow_init)
- .call(arrow_formatting);
+ .append("svg:line")
+ .call(function(sel) { arrow_init(sel, settings); })
+ .call(function(sel) { arrow_formatting(sel, settings, scales); });
// Add ellipses
if (settings.ellipses) {
- var ellipse = chart_body
- .selectAll(".ellipse")
- .data(settings.ellipses_data);
- ellipse.enter()
- .append("svg:path")
- .call(ellipse_init)
- .call(ellipse_formatting);
+ var ellipse = chart_body
+ .selectAll(".ellipse")
+ .data(settings.ellipses_data);
+ ellipse.enter()
+ .append("svg:path")
+ .call(ellipse_init)
+ .call(function(sel) { ellipse_formatting(sel, settings, scales); });
}
// Add text labels
if (settings.has_labels) {
var labels = chart_body.selectAll(".point-label")
- .data(data, key);
+ .data(data, key);
labels.enter()
- .append("text")
- .call(label_init)
- .call(label_formatting)
- .call(drag);
+ .append("text")
+ .call(label_init)
+ .call(function(sel) { label_formatting(sel, settings, scales); })
+ .call(drag);
}
// Legends
+ var legend = svg.append("g").attr("class", "legend")
+ .style("font-size", settings.legend_font_size);
+
if (settings.has_legend && settings.legend_width > 0) {
- var legend = svg.append("g").attr("class", "legend");
+ dims = setup_legend_sizes(dims, scales, settings);
// Color legend
- if (settings.has_color_var) {
- add_color_legend.svg = svg;
- add_color_legend(legend);
- }
+ if (settings.has_color_var)
+ add_color_legend(svg, dims, settings, scales);
// Symbol legend
- if (settings.has_symbol_var) {
- add_symbol_legend.svg = svg;
- add_symbol_legend(legend);
- }
+ if (settings.has_symbol_var)
+ add_symbol_legend(svg, dims, settings, scales);
// Size legend
- if (settings.has_size_var) add_size_legend(legend);
+ if (settings.has_size_var)
+ add_size_legend(svg, dims, settings, scales);
+ }
+
+ // Tools menu
+ if(settings.menu) {
+
+ // Gear icon
+ var gear = svg.append("g")
+ .attr("class", "gear-menu")
+ .attr("transform", "translate(" + (width - 36) + ", 6)");
+ gear.append("rect")
+ .attr("class", "gear-toggle")
+ .attr("width", "25")
+ .attr("height", "25")
+ .style("fill", "#FFFFFF");
+ gear.append("path")
+ .attr("d", gear_path())
+ .attr("transform", "translate(-3,3)")
+ .style("fill", "#666666");
+
+ var menu_parent = d3.select(svg.node().parentNode);
+ menu_parent.style("position", "relative");
+ var menu = menu_parent.select(".scatterD3-menu");
+
+ menu.attr("id", "scatterD3-menu-" + settings.html_id);
+
+ menu.append("li")
+ .append("a")
+ .on("click", reset_zoom)
+ .html("Reset zoom");
+
+ menu.append("li")
+ .append("a")
+ .on("click", function() { export_svg(this, svg, settings); })
+ .html("Export to SVG");
+
+ if (settings.lasso) {
+ menu.append("li")
+ .append("a")
+ .attr("class", "lasso-entry")
+ .on("click", function () {lasso_toggle(svg, settings, scales, zoom);})
+ .html("Toggle lasso on");
+ }
+
+ if (settings.has_labels) {
+ menu.append("li")
+ .append("a")
+ .on("click", function() { export_labels_position(this, data, settings, scales); })
+ .html("Export labels positions");
+ }
+
+ gear.on("click", function(d, i){
+ var menu = d3.select("#scatterD3-menu-" + settings.html_id);
+ var gear = svg.select(".gear-menu");
+ if (!menu.classed("open")) {
+ menu.transition().duration(300)
+ .style("opacity", "0.95")
+ .style("width", "165px");
+ gear.classed("selected", true);
+ menu.classed("open", true);
+ } else {
+ menu.transition().duration(300)
+ .style("opacity", "0")
+ .style("width", "0px");
+ gear.classed("selected", false);
+ menu.classed("open", false);
+ }
+ });
}
});
@@ -828,260 +287,308 @@ function scatterD3() {
// Update chart with transitions
function update_settings(old_settings) {
+ var chart_body = svg.select(".chart-body");
if (old_settings.labels_size != settings.labels_size)
svg.selectAll(".point-label").transition().style("font-size", settings.labels_size + "px");
- if (old_settings.point_size != settings.point_size)
- svg.selectAll(".dot").transition().call(dot_formatting);
+ if (old_settings.point_size != settings.point_size ||
+ old_settings.point_opacity != settings.point_opacity) {
+ svg.selectAll(".dot").transition()
+ .call(function(sel) { dot_formatting(sel, settings, scales); });
+ }
if (old_settings.has_labels != settings.has_labels) {
if (!settings.has_labels) {
svg.selectAll(".point-label").remove();
}
if (settings.has_labels) {
- var chart_body = svg.select(".chart-body");
var labels = chart_body.selectAll(".point-label")
- .data(data, key);
+ .data(data, key);
labels.enter()
- .append("text")
- .call(label_init)
- .call(label_formatting)
- .call(drag);
+ .append("text")
+ .call(label_init)
+ .call(function(sel) { label_formatting(sel, settings, scales); })
+ .call(drag);
}
}
if (old_settings.unit_circle != settings.unit_circle) {
if (!settings.unit_circle) {
var circle = svg.select(".unit-circle");
- circle.transition().duration(1000).call(unit_circle_init)
- .style("opacity", "0").remove();
+ circle.transition().duration(1000)
+ .call(function(sel) { add_unit_circle(sel, scales); })
+ .style("opacity", "0").remove();
}
if (settings.unit_circle) {
- var chart_body = svg.select(".chart-body");
chart_body.append('svg:ellipse')
- .attr('class', 'unit-circle')
- .style("opacity", "0");
+ .attr('class', 'unit-circle')
+ .style("opacity", "0");
}
}
+ if (settings.menu) {
+ var menu_parent = d3.select(svg.node().parentNode);
+ menu_parent.style("position", "relative");
+ var menu = menu_parent.select(".scatterD3-menu");
+ menu.attr("id", "scatterD3-menu-" + settings.html_id);
+ }
};
// Update data with transitions
function update_data() {
- if (settings.has_legend_changed && settings.legend_width > 0)
- resize_chart();
-
- setup_sizes();
- setup_scales();
-
- var t0 = svg.transition().duration(1000);
- svg.select(".x.axis .axis-label").text(settings.xlab);
- t0.select(".x.axis").call(xAxis);
- t0.select(".zeroline.vline").attr("d", zeroline([{x:0, y:y.domain()[0]}, {x:0, y:y.domain()[1]}]));
- svg.select(".y.axis .axis-label").text(settings.ylab);
- t0.select(".y.axis").call(yAxis);
- t0.select(".zeroline.hline").attr("d", zeroline([{x:x.domain()[0], y:0}, {x:x.domain()[1], y:0}]));
- svg.select(".pane").call(zoom);
- zoom.x(x);
- zoom.y(y);
- // Unit circle
- if (settings.unit_circle) t0.select(".unit-circle").call(unit_circle_init);
-
- var chart_body = svg.select(".chart-body");
-
- // Add points
- var dot = chart_body
- .selectAll(".dot")
- .data(data.filter(point_filter), key);
- dot.enter().append("path").call(dot_init);
- dot.transition().duration(1000).call(dot_formatting);
- dot.exit().transition().duration(1000).attr("transform", "translate(0,0)").remove();
- // Add arrows
- var arrow = chart_body
- .selectAll(".arrow")
- .data(data.filter(arrow_filter), key);
- arrow.enter().append("svg:line").call(arrow_init)
- .style("opacity", "0")
- .transition().duration(1000)
- .style("opacity", "1");
- arrow.transition().duration(1000).call(arrow_formatting);
- arrow.exit().transition().duration(1000).style("opacity", "0").remove();
-
- // Add ellipses
- if (settings.ellipses || settings.ellipses_changed) {
- var ellipse = chart_body
- .selectAll(".ellipse")
- .data(settings.ellipses_data);
- ellipse.enter().append("path").call(ellipse_init)
- .style("opacity", "0")
- .transition().duration(1000)
- .style("opacity", "1");
- ellipse.transition().duration(1000).call(ellipse_formatting);
- ellipse.exit().transition().duration(1000).style("opacity", "0").remove();
- }
-
- if (settings.has_labels) {
- var labels = chart_body.selectAll(".point-label")
- .data(data, key);
- labels.enter().append("text").call(label_init).call(drag);
- labels.transition().duration(1000).call(label_formatting);
- labels.exit().transition().duration(1000).attr("transform", "translate(0,0)").remove();
- }
-
- if (settings.legend_changed) {
-
- // Remove existing legends
- svg.select(".legend").remove();
- var legend = svg.append("g").attr("class", "legend");
-
- // Recreate them
- if (settings.has_legend && settings.legend_width > 0) {
- // Color legend
- if (settings.has_color_var) {
- add_color_legend.svg = svg;
- add_color_legend(legend);
- }
- // Symbol legend
- if (settings.has_symbol_var) {
- add_symbol_legend.svg = svg;
- add_symbol_legend(legend);
- }
- // Size legend
- if (settings.has_size_var) add_size_legend(legend);
- }
- }
-
- lasso_off(svg);
- d3.select("#" + settings.dom_id_lasso_toggle).classed("active", false);
+ dims = setup_sizes(width, height, settings);
+ scales = setup_scales(dims, settings, data);
+
+ // Change axes labels
+ svg.select(".x-axis-label").text(settings.xlab);
+ svg.select(".y-axis-label").text(settings.ylab);
+
+ var t0 = svg.transition().duration(1000);
+ t0.call(resize_plot);
+
+ var chart_body = svg.select(".chart-body");
+ // Add lines
+ if (settings.lines !== null) {
+ var line = chart_body.selectAll(".line")
+ .data(HTMLWidgets.dataframeToD3(settings.lines));
+ line.enter().append("path").call(line_init)
+ .style("opacity", "0")
+ .merge(line)
+ .transition().duration(1000)
+ .call(function(sel) {
+ line_formatting(sel, dims, settings, scales);
+ })
+ .style("opacity", "1");
+ line.exit().transition().duration(1000).style("opacity", "0").remove();
+ }
+
+ // Unit circle
+ if (settings.unit_circle) {
+ t0.select(".unit-circle")
+ .call(function(sel) { add_unit_circle(sel, scales); });
+ }
+
+ // Add points
+ var dot = chart_body.selectAll(".dot")
+ .data(data.filter(point_filter), key);
+ dot.enter().append("path").call(function(sel) {dot_init(sel, settings, scales);})
+ .merge(dot).transition().duration(1000).call(function(sel) {dot_formatting(sel, settings, scales);});
+ dot.exit().transition().duration(1000).attr("transform", "translate(0,0)").remove();
+ // Add arrows
+ var arrow = chart_body.selectAll(".arrow")
+ .data(data.filter(arrow_filter), key);
+ arrow.enter().append("svg:line").call(function(sel) {arrow_init(sel, settings);})
+ .style("opacity", "0")
+ .merge(arrow)
+ .transition().duration(1000)
+ .call(function(sel) { arrow_formatting(sel, settings, scales); })
+ .style("opacity", "1");
+ arrow.exit().transition().duration(1000).style("opacity", "0").remove();
+
+ // Add ellipses
+ if (settings.ellipses || settings.ellipses_changed) {
+ var ellipse = chart_body.selectAll(".ellipse")
+ .data(settings.ellipses_data);
+ ellipse.enter().append("path").call(ellipse_init)
+ .style("opacity", "0")
+ .merge(ellipse)
+ .transition().duration(1000)
+ .call(function(sel) { ellipse_formatting(sel, settings, scales);})
+ .style("opacity", "1");
+ ellipse.exit().transition().duration(1000).style("opacity", "0").remove();
+ }
+
+ if (settings.has_labels) {
+ var labels = chart_body.selectAll(".point-label")
+ .data(data, key);
+ labels.enter().append("text").call(label_init).call(drag)
+ .merge(labels).transition().duration(1000).call(function(sel) { label_formatting(sel, settings, scales); });
+ labels.exit().transition().duration(1000).attr("transform", "translate(0,0)").remove();
+ }
+
+ if (settings.legend_changed) {
+ var legend = svg.select(".legend");
+ dims = setup_legend_sizes(dims, scales, settings);
+
+ // Move color legend
+ if(settings.has_color_var && settings.had_color_var && !settings.col_changed) {
+ legend.call(function(sel) {
+ move_color_legend(sel, dims, 1000); });
+ }
+ // Replace color legend
+ if(settings.has_color_var && settings.had_color_var && settings.col_changed) {
+ legend.call(function(sel) {
+ remove_color_legend(sel);
+ add_color_legend(svg, dims, settings, scales, 1000);
+ });
+ }
+ // Add color legend
+ if(settings.has_color_var && !settings.had_color_var) {
+ add_color_legend(svg, dims, settings, scales, 1000);
+ }
+ // Remove color legend
+ if(!settings.has_color_var && settings.had_color_var) {
+ legend.call(remove_color_legend);
+ }
+
+ // Move symbol legend
+ if(settings.has_symbol_var && settings.had_symbol_var && !settings.symbol_changed) {
+ legend.call(function(sel) {
+ move_symbol_legend(sel, dims, 1000); });
+ }
+ // Replace symbol legend
+ if(settings.has_symbol_var && settings.had_symbol_var && settings.symbol_changed) {
+ legend.call(function(sel) {
+ remove_symbol_legend(sel);
+ add_symbol_legend(svg, dims, settings, scales, 1000);
+ });
+ }
+ // Add symbol legend
+ if(settings.has_symbol_var && !settings.had_symbol_var) {
+ add_symbol_legend(svg, dims, settings, scales, 1000);
+ }
+ // Remove symbol legend
+ if(!settings.has_symbol_var && settings.had_symbol_var) {
+ legend.call(remove_symbol_legend);
+ }
+
+ // Move size legend
+ if(settings.has_size_var && settings.had_size_var && !settings.size_changed) {
+ legend.call(function(sel) {
+ move_size_legend(sel, dims, 1000); });
+ }
+ // Replace size legend
+ if(settings.has_size_var && settings.had_size_var && settings.size_changed) {
+ legend.call(function(sel) {
+ remove_size_legend(sel);
+ add_size_legend(svg, dims, settings, scales, 1000);
+ });
+ }
+ // Add size legend
+ if(settings.has_size_var && !settings.had_size_var) {
+ add_size_legend(svg, dims, settings, scales, 1000);
+ }
+ // Remove size legend
+ if(!settings.has_size_var && settings.had_size_var) {
+ legend.call(remove_size_legend);
+ }
+
+ }
+ // Reset zoom
+ svg.select(".root")
+ .transition().delay(1000).duration(0)
+ .call(zoom.transform, d3.zoomIdentity);
+
+
+
+
+ lasso_off(svg, settings, zoom);
};
+ // Dynamically resize plot area
+ function resize_plot(selection) {
+ // Change svg attributes
+ selection.selectAll(".root")
+ .attr("width", dims.width)
+ .attr("height", dims.height);
+ selection.selectAll(".root")
+ .select("rect")
+ .attr("width", dims.width)
+ .attr("height", dims.height);
+ selection.selectAll(".chart-body")
+ .attr("width", dims.width)
+ .attr("height", dims.height);
+ selection.select(".x.axis")
+ .attr("transform", "translate(0," + dims.height + ")");
+ selection.select(".x-axis-label")
+ .attr("transform", "translate(" + (dims.width - 5) + "," + (dims.height - 6) + ")");
+ selection.select(".x.axis").call(scales.xAxis);
+ selection.select(".y.axis").call(scales.yAxis);
+
+ if (settings.unit_circle) {
+ selection.select(".unit-circle")
+ .call(function(sel) { add_unit_circle(sel, scales); });
+ }
+ }
+
// Dynamically resize chart elements
function resize_chart () {
// recompute sizes
- setup_sizes();
- // recompute scales and zoom
- var cache_translate = zoom.translate();
- var cache_scale = zoom.scale();
- zoom.scale(1).translate([0, 0]);
- x.range([0, dims.width]);
- y.range([dims.height, 0]);
- xAxis.scale(x).tickSize(-dims.height);
- yAxis.scale(y).tickSize(-dims.width);
- zoom.x(x);
- zoom.y(y);
- zoom.translate(cache_translate);
- zoom.scale(cache_scale);
- // Change svg attributes
- svg.select(".root").attr("width", dims.width).attr("height", dims.height);
- svg.select(".cliprect").attr("width", dims.width).attr("height", dims.height);
- svg.select(".pane").attr("width", dims.width).attr("height", dims.height).call(zoom);
- svg.select(".chart-body").attr("width", dims.width).attr("height", dims.height);
- svg.select(".x.axis").attr("transform", "translate(0," + dims.height + ")").call(xAxis);
- svg.select(".x.axis .axis-label").attr("x", dims.width - 5);
- svg.select(".y.axis").call(yAxis);
- svg.select(".unit-circle").call(unit_circle_init);
-
- svg.selectAll(".dot").attr("transform", translation);
- svg.selectAll(".arrow").call(draw_arrow);
- svg.selectAll(".ellipse").call(ellipse_formatting);
- if (settings.has_labels) {
- svg.selectAll(".point-label")
- .attr("transform", translation);
- }
- // Move zerolines
- var zeroline = d3.svg.line()
- .x(function(d) {return x(d.x)})
- .y(function(d) {return y(d.y)});
- svg.select(".zeroline.hline").attr("d", zeroline([{x:x.domain()[0], y:0}, {x:x.domain()[1], y:0}]));
- svg.select(".zeroline.vline").attr("d", zeroline([{x:0, y:y.domain()[0]}, {x:0, y:y.domain()[1]}]));
+ dims = setup_sizes(width, height, settings);
+ dims = setup_legend_sizes(dims, scales, settings);
+ // recompute x and y scales
+ scales.x.range([0, dims.width]);
+ scales.x_orig.range([0, dims.width]);
+ scales.y.range([dims.height, 0]);
+ scales.y_orig.range([dims.height, 0]);
+ scales.xAxis = d3.axisBottom(scales.x).tickSize(-dims.height);
+ scales.yAxis = d3.axisLeft(scales.y).tickSize(-dims.width);
+
+ svg.call(resize_plot);
+
+ svg.select(".root")
+ .call(zoom.transform,
+ d3.zoomTransform(svg.select(".root").node()));
+
// Move legends
- if (settings.has_color_var) {
- svg.select(".color-legend-label")
- .attr("transform", "translate(" + dims.legend_x + "," + margin.legend_top + ")");
- svg.select(".color-legend")
- .attr("transform", "translate(" + dims.legend_x + "," + (margin.legend_top + 12) + ")");
- }
- if (settings.has_symbol_var) {
- svg.select(".symbol-legend-label")
- .attr("transform", "translate(" + dims.legend_x + "," + margin.symbol_legend_top + ")");
- svg.select(".symbol-legend")
- .attr("transform", "translate(" + (dims.legend_x + 8) + "," + (margin.symbol_legend_top + 14) + ")");
- }
- if (settings.has_size_var) {
- svg.select(".size-legend-label")
- .attr("transform", "translate(" + dims.legend_x + "," + margin.size_legend_top + ")");
- svg.select(".size-legend")
- .attr("transform", "translate(" + (dims.legend_x + 8) + "," + (margin.size_legend_top + 14) + ")");
+ if (settings.has_legend && settings.legend_width > 0) {
+ var legend = svg.select(".legend");
+ if (settings.has_color_var)
+ move_color_legend(legend, dims, 0);
+ if (settings.has_symbol_var)
+ move_symbol_legend(legend, dims, 0);
+ if (settings.has_size_var)
+ move_size_legend(legend, dims, 0);
+ }
+ // Move menu
+ if (settings.menu) {
+ svg.select(".gear-menu")
+ .attr("transform", "translate(" + (width - 40) + "," + 10 + ")");
}
};
+
// Add controls handlers for shiny
chart.add_controls_handlers = function() {
-
// Zoom reset
d3.select("#" + settings.dom_id_reset_zoom)
- .on("click", function() {
- d3.transition().duration(750).tween("zoom", function() {
- var ix = d3.interpolate(x.domain(), [min_x - gap_x, max_x + gap_x]),
- iy = d3.interpolate(y.domain(), [min_y - gap_y, max_y + gap_y]);
- return function(t) {
- zoom.x(x.domain(ix(t))).y(y.domain(iy(t)));
- zoomed(reset = true);
- };
- })
- });
+ .on("click", reset_zoom);
// SVG export
d3.select("#" + settings.dom_id_svg_export)
- .on("click", function(){
- var svg_content = svg
- .attr("xmlns", "http://www.w3.org/2000/svg")
- .attr("version", 1.1)
- .node().parentNode.innerHTML;
- svg_content = svg_content.replace(/clip-path="url\(.*?(#.*?)\)"/,
- 'clip-path="url($1)"');
- var imageUrl = "data:image/octet-stream;base64,\n" + btoa(svg_content);
- d3.select(this)
- .attr("download", "scatterD3.svg")
- .attr("href", imageUrl);
- });
+ .on("click", function() { export_svg(this, svg, settings); });
// Lasso toggle
d3.select("#" + settings.dom_id_lasso_toggle)
- .on("click", function(){
- if (!d3.select(this).classed("active") && settings.lasso) {
- lasso_on(svg);
- }
- if (d3.select(this).classed("active") && settings.lasso) {
- lasso_off(svg);
- }
- })
+ .on("click", function () {lasso_toggle(svg, settings, scales, zoom);});
};
chart.add_global_listeners = function() {
- // Toogle zoom and lasso behaviors when shift is pressed
- var parent = d3.select("#scatterD3-svg-" + settings.html_id).node().parentNode;
- d3.select(parent)
- .attr("tabindex", 0)
- .on("keydown", function() {
- if (d3.event.keyIdentifier == "Shift") {
- if (settings.lasso) {
- lasso_on(svg);
- }
- }
- })
- .on("keyup", function() {
- if (d3.event.keyIdentifier == "Shift") {
- if (settings.lasso) {
- lasso_off(svg);
- }
- }
- })
+ // Toogle zoom and lasso behaviors when shift is pressed
+ var parent = d3.select("#scatterD3-svg-" + settings.html_id).node().parentNode;
+ d3.select(parent)
+ .attr("tabindex", 0)
+ .on("keydown", function() {
+ var key = d3.event.key !== undefined ? d3.event.key : d3.event.keyIdentifier;
+ if (key == "Shift") {
+ if (settings.lasso) {
+ lasso_on(svg, settings, scales, zoom);
+ }
+ }
+ })
+ .on("keyup", function() {
+ var key = d3.event.key !== undefined ? d3.event.key : d3.event.keyIdentifier;
+ if (key == "Shift") {
+ if (settings.lasso) {
+ lasso_off(svg, settings, zoom);
+ }
+ }
+ });
- }
+ };
// resize
chart.resize = function() {
resize_chart();
- }
+ };
// settings getter/setter
chart.data = function(value, redraw) {
@@ -1096,9 +603,6 @@ function scatterD3() {
if (!arguments.length) return settings;
if (Object.keys(settings).length === 0) {
settings = value;
- // update dims and scales
- setup_sizes();
- setup_scales();
} else {
var old_settings = settings;
settings = value;
@@ -1111,7 +615,7 @@ function scatterD3() {
if (!arguments.length) return svg;
svg = value;
return chart;
- }
+ };
// width getter/setter
chart.width = function(value) {
@@ -1138,101 +642,133 @@ HTMLWidgets.widget({
type: 'output',
- initialize: function(el, width, height) {
+ factory: function(el, width, height) {
if (width < 0) width = 0;
if (height < 0) height = 0;
// Create root svg element
var svg = d3.select(el).append("svg");
svg
- .attr("width", width)
- .attr("height", height)
- .attr("class", "scatterD3")
- .append("style")
- .text(".scatterD3 {font: 10px sans-serif;}" +
- ".scatterD3 .axis line, .axis path { stroke: #000; fill: none; shape-rendering: CrispEdges;} " +
- ".scatterD3 .axis .tick line { stroke: #ddd;} " +
- ".scatterD3 .axis text { fill: #000; } " +
- ".scatterD3 .zeroline { stroke-width: 1; stroke: #444; stroke-dasharray: 5,5;} ");
+ .attr("width", width)
+ .attr("height", height)
+ .attr("class", "scatterD3")
+ .append("style")
+ .text(".scatterD3 {font: 11px Open Sans, Droid Sans, Helvetica, Verdana, sans-serif;}" +
+ ".scatterD3 .axis line, .axis path { stroke: #000; fill: none; shape-rendering: CrispEdges;} " +
+ ".scatterD3 .axis .tick line { stroke: #ddd;} " +
+ ".scatterD3 .axis text { fill: #000; }");
// Create tooltip content div
var tooltip = d3.select(".scatterD3-tooltip");
if (tooltip.empty()) {
tooltip = d3.select("body")
- .append("div")
- .style("visibility", "hidden")
- .attr("class", "scatterD3-tooltip");
+ .append("div")
+ .style("visibility", "hidden")
+ .attr("class", "scatterD3-tooltip");
}
- // Create scatterD3 instance
- return scatterD3().width(width).height(height).svg(svg);
- },
-
- resize: function(el, width, height, scatter) {
-
- if (width < 0) width = 0;
- if (height < 0) height = 0;
- // resize root svg element
- var svg = d3.select(el).select("svg");
- svg
- .attr("width", width)
- .attr("height", height);
- // resize chart
- scatter.width(width).height(height).svg(svg).resize();
- },
-
- renderValue: function(el, obj, scatter) {
- // Check if update or redraw
- var first_draw = (Object.keys(scatter.settings()).length === 0);
- var redraw = first_draw || !obj.settings.transitions;
- var svg = d3.select(el).select("svg").attr("id", "scatterD3-svg-" + obj.settings.html_id);
- scatter = scatter.svg(svg);
-
- // convert data to d3 format
- data = HTMLWidgets.dataframeToD3(obj.data);
-
- // If no transitions, remove chart and redraw it
- if (!obj.settings.transitions) {
- svg.selectAll("*:not(style)").remove();
+ // Create menu div
+ var menu = d3.select(el).select(".scatterD3-menu");
+ if (menu.empty()) {
+ menu = d3.select(el).append("ul")
+ .attr("class", "scatterD3-menu");
}
- // Complete draw
- if (redraw) {
- scatter = scatter.data(data, redraw);
- obj.settings.redraw = true;
- scatter = scatter.settings(obj.settings);
- // add controls handlers and global listeners for shiny apps
- scatter.add_controls_handlers();
- scatter.add_global_listeners();
- // draw chart
- d3.select(el)
- .call(scatter);
- }
- // Update only
- else {
- // Check what did change
- obj.settings.has_legend_changed = scatter.settings().has_legend != obj.settings.has_legend;
- obj.settings.has_labels_changed = scatter.settings().has_labels != obj.settings.has_labels;
- obj.settings.size_range_changed = scatter.settings().size_range != obj.settings.size_range;
- obj.settings.ellipses_changed = scatter.settings().ellipses != obj.settings.ellipses;
- function changed(varname) {
- return obj.settings.hashes[varname] != scatter.settings().hashes[varname];
- };
- obj.settings.x_changed = changed("x");
- obj.settings.y_changed = changed("y");
- obj.settings.lab_changed = changed("lab");
- obj.settings.legend_changed = changed("col_var") || changed("symbol_var") ||
- changed("size_var") || obj.settings.size_range_changed;
- obj.settings.data_changed = obj.settings.x_changed || obj.settings.y_changed ||
- obj.settings.lab_changed || obj.settings.legend_changed ||
- obj.settings.has_labels_changed || changed("ellipses_data") ||
- obj.settings.ellipses_changed;
- obj.settings.opacity_changed = changed("point_opacity");
- obj.settings.subset_changed = changed("key_var");
- scatter = scatter.settings(obj.settings);
- // Update data only if needed
- if (obj.settings.data_changed) scatter = scatter.data(data, redraw);
- }
+ // Create scatterD3 instance
+ var scatter = scatterD3().width(width).height(height).svg(svg);
+
+ return({
+ resize: function(width, height) {
+
+ if (width < 0) width = 0;
+ if (height < 0) height = 0;
+ // resize root svg element
+ var svg = d3.select(el).select("svg");
+ svg
+ .attr("width", width)
+ .attr("height", height);
+ // resize chart
+ scatter.width(width).height(height).svg(svg).resize();
+ },
+
+ renderValue: function(obj) {
+ // Check if update or redraw
+ var first_draw = (Object.keys(scatter.settings()).length === 0);
+ var redraw = first_draw || !obj.settings.transitions;
+ var svg = d3.select(el).select("svg").attr("id", "scatterD3-svg-" + obj.settings.html_id);
+ scatter = scatter.svg(svg);
+
+ // convert data to d3 format
+ var data = HTMLWidgets.dataframeToD3(obj.data);
+
+ // If no transitions, remove chart and redraw it
+ if (!obj.settings.transitions) {
+ svg.selectAll("*:not(style)").remove();
+ menu.selectAll("li").remove();
+ }
+
+ // Complete draw
+ if (redraw) {
+ scatter = scatter.data(data, redraw);
+ obj.settings.redraw = true;
+ scatter = scatter.settings(obj.settings);
+ // add controls handlers and global listeners for shiny apps
+ scatter.add_controls_handlers();
+ scatter.add_global_listeners();
+ // draw chart
+ d3.select(el)
+ .call(scatter);
+ }
+ // Update only
+ else {
+ // Array equality test
+ function array_equal (a1, a2) {
+ return a1.length == a2.length && a1.every(function(v,i) { return v === a2[i];});
+ }
+
+ // Check what did change
+ obj.settings.has_legend_changed = scatter.settings().has_legend != obj.settings.has_legend;
+ obj.settings.has_labels_changed = scatter.settings().has_labels != obj.settings.has_labels;
+ obj.settings.size_range_changed = !array_equal(scatter.settings().size_range, obj.settings.size_range);
+ obj.settings.ellipses_changed = scatter.settings().ellipses != obj.settings.ellipses;
+ obj.settings.colors_changed = scatter.settings().colors != obj.settings.colors;
+
+ obj.settings.had_color_var = scatter.settings().has_color_var;
+ obj.settings.had_symbol_var = scatter.settings().has_symbol_var;
+ obj.settings.had_size_var = scatter.settings().has_size_var;
+
+ function changed(varname) {
+ return obj.settings.hashes[varname] != scatter.settings().hashes[varname];
+ };
+ obj.settings.x_changed = changed("x");
+ obj.settings.y_changed = changed("y");
+ obj.settings.lab_changed = changed("lab");
+ obj.settings.col_changed = changed("col_var") ||
+ obj.settings.colors_changed;
+ obj.settings.size_changed = changed("size_var") ||
+ obj.settings.size_range_changed;
+ obj.settings.symbol_changed = changed("symbol_var");
+ obj.settings.legend_changed = obj.settings.col_changed ||
+ obj.settings.symbol_changed ||
+ obj.settings.size_changed;
+ obj.settings.data_changed = obj.settings.x_changed ||
+ obj.settings.y_changed ||
+ obj.settings.lab_changed ||
+ obj.settings.legend_changed ||
+ obj.settings.has_labels_changed ||
+ changed("ellipses_data") ||
+ obj.settings.ellipses_changed ||
+ changed("opacity_var") ||
+ changed("lines");
+
+ // Update settings
+ scatter = scatter.settings(obj.settings);
+ // Update data only if needed
+ if (obj.settings.data_changed) scatter = scatter.data(data, redraw);
+ }
+ },
+
+ s: scatter
+ });
}
-
});
diff --git a/inst/htmlwidgets/scatterD3.yaml b/inst/htmlwidgets/scatterD3.yaml
index c8f6be9..fc155c2 100644
--- a/inst/htmlwidgets/scatterD3.yaml
+++ b/inst/htmlwidgets/scatterD3.yaml
@@ -1,13 +1,49 @@
dependencies:
- name: d3
- version: 3.5.6
- src: htmlwidgets/lib
+ version: 4.2.6
+ src: htmlwidgets/lib/d3
script:
- - d3-3.5.6.min.js
+# - d3-4.2.6.min.js
+ - d3-color.v1.min.js
+ - d3-array.v1.min.js
+ - d3-collection.v1.min.js
+ - d3-format.v1.min.js
+ - d3-dispatch.v1.min.js
+ - d3-ease.v1.min.js
+ - d3-interpolate.v1.min.js
+ - d3-selection.v1.min.js
+ - d3-scale.v1.min.js
+ - d3-timer.v1.min.js
+ - d3-transition.v1.min.js
+ - d3-drag.v1.min.js
+ - d3-path.v1.min.js
+ - d3-shape.v1.min.js
+ - d3-axis.v1.min.js
- d3-legend.min.js
- stylesheet: scatterD3.css
+ - name: d3-zoom-patched
+ version: 0.0
+ src: htmlwidgets/lib/d3
+ script:
+ - d3-zoom-patched.min.js
- name: d3.lasso-plugin
version: 1.0.0
src: htmlwidgets/lib/d3-lasso-plugin
script: lasso.js
stylesheet: lasso.css
+ - name: scatterD3
+ version: 0.7
+ src: htmlwidgets/
+ stylesheet: scatterD3.css
+ script:
+ - scatterD3-utils.js
+ - scatterD3-setup.js
+ - scatterD3-axes.js
+ - scatterD3-dots.js
+ - scatterD3-arrows.js
+ - scatterD3-labels.js
+ - scatterD3-lines.js
+ - scatterD3-ellipses.js
+ - scatterD3-legend.js
+ - scatterD3-lasso.js
+ - scatterD3-exports.js
+
diff --git a/man/scatterD3.Rd b/man/scatterD3.Rd
index 2518a42..15345c6 100644
--- a/man/scatterD3.Rd
+++ b/man/scatterD3.Rd
@@ -7,50 +7,72 @@
D3.js was created by Michael Bostock. See \url{http://d3js.org/}
}
\usage{
-scatterD3(x, y, lab = NULL, point_size = 64, labels_size = 10,
- point_opacity = 1, fixed = FALSE, col_var = NULL, colors = NULL,
- ellipses = FALSE, ellipses_level = 0.95, symbol_var = NULL,
- size_var = NULL, size_range = c(10, 300), col_lab = NULL,
- symbol_lab = NULL, size_lab = NULL, key_var = NULL, type_var = NULL,
- unit_circle = FALSE, tooltips = TRUE, tooltip_text = NULL,
- xlab = NULL, ylab = NULL, html_id = NULL, width = NULL,
- height = NULL, legend_width = 150, xlim = NULL, ylim = NULL,
- dom_id_reset_zoom = "scatterD3-reset-zoom",
+scatterD3(x, y, data = NULL, lab = NULL, point_size = 64,
+ labels_size = 10, point_opacity = 1, hover_size = 1,
+ hover_opacity = NULL, fixed = FALSE, col_var = NULL,
+ col_continuous = NULL, colors = NULL, ellipses = FALSE,
+ ellipses_level = 0.95, symbol_var = NULL, size_var = NULL,
+ size_range = c(10, 300), col_lab = NULL, symbol_lab = NULL,
+ size_lab = NULL, key_var = NULL, type_var = NULL, opacity_var = NULL,
+ unit_circle = FALSE, url_var = NULL, tooltips = TRUE,
+ tooltip_text = NULL, xlab = NULL, ylab = NULL, html_id = NULL,
+ width = NULL, height = NULL, legend_width = 150, left_margin = 30,
+ xlim = NULL, ylim = NULL, dom_id_reset_zoom = "scatterD3-reset-zoom",
dom_id_svg_export = "scatterD3-svg-export",
dom_id_lasso_toggle = "scatterD3-lasso-toggle", transitions = FALSE,
- lasso = FALSE, lasso_callback = NULL)
+ menu = TRUE, lasso = FALSE, lasso_callback = NULL,
+ click_callback = NULL, zoom_callback = NULL, lines = data.frame(slope =
+ c(0, Inf), intercept = c(0, 0), stroke_dasharray = c(5, 5)),
+ axes_font_size = "100\%", legend_font_size = "100\%")
}
\arguments{
-\item{x}{numerical vector of x values}
+\item{x}{numerical vector of x values, or variable name if data is not NULL}
-\item{y}{numerical vector of y values}
+\item{y}{numerical vector of y values, or variable name if data is not NULL}
-\item{lab}{optional character vector of text labels}
+\item{data}{default dataset to use for plot.}
+
+\item{lab}{optional character vector of text labels, or variable name if
+data is not NULL}
\item{point_size}{points size. Ignored if size_var is not NULL.}
\item{labels_size}{text labels size}
-\item{point_opacity}{points opacity, as an integer (same opacity for all points) or a vector of integers}
+\item{point_opacity}{points opacity, as an integer (same opacity for all
+points) or a vector of integers, or variable name if data is not NULL}
+
+\item{hover_size}{factor for changing size when hovering points}
+
+\item{hover_opacity}{points opacity when hovering}
\item{fixed}{force a 1:1 aspect ratio}
-\item{col_var}{optional vector for points color mapping}
+\item{col_var}{optional vector for points color mapping, or variable name
+if data is not NULL}
+
+\item{col_continuous}{specify if the color scale must be continuous. By
+default, if \code{col_var} is numeric, not a factor, and has more than
+6 unique values, it is considered as continuous.}
-\item{colors}{vector of custom points colors. Colors must be
-defined as an hexadecimal string (eg "#FF0000"). If
-\code{colors} is a named list or vector, then the colors will
-be associated with their name within \code{col_var}.}
+\item{colors}{vector of custom points colors. Colors must be defined as an
+hexadecimal string (eg "#FF0000"). If \code{colors} is a named list or
+a named vector, then the colors will be associated with their name
+within \code{col_var}. Ignored for a continuous color scale.}
-\item{ellipses}{draw confidence ellipses for points or the different color mapping groups}
+\item{ellipses}{draw confidence ellipses for points or the different color
+mapping groups}
\item{ellipses_level}{confidence level for ellipses (0.95 by default)}
-\item{symbol_var}{optional vector for points symbol mapping}
+\item{symbol_var}{optional vector for points symbol mapping, or variable
+name if data is not NULL}
-\item{size_var}{optional vector for points size mapping}
+\item{size_var}{optional vector for points size mapping, or variable name
+if data is not NULL}
-\item{size_range}{numeric vector of length 2, giving the minimum and maximum point sizes when mapping with size_var}
+\item{size_range}{numeric vector of length 2, giving the minimum and
+maximum point sizes when mapping with size_var}
\item{col_lab}{color legend title}
@@ -58,43 +80,82 @@ be associated with their name within \code{col_var}.}
\item{size_lab}{size legend title}
-\item{key_var}{optional vector of rows ids. This is passed as a key to d3, and is only added in shiny apps where displayed rows are filtered interactively.}
+\item{key_var}{optional vector of rows ids, or variable name if data is not
+NULL. This is passed as a key to d3, and is only added in shiny apps
+where displayed rows are filtered interactively.}
-\item{type_var}{optional vector of points type : "point" for adot (default), "arrow" for an arrow starting from the origin.}
+\item{type_var}{optional vector of points type : "point" for adot
+(default), "arrow" for an arrow starting from the origin.}
+
+\item{opacity_var}{optional vector of points opacity (values between 0 and
+1)}
\item{unit_circle}{set tot TRUE to draw a unit circle}
+\item{url_var}{optional vector of URLs to be opened when a point is clicked}
+
\item{tooltips}{logical value to display tooltips when hovering points}
\item{tooltip_text}{optional character vector of tooltips text}
\item{xlab}{x axis label}
-\item{ylab}{y axis label}
+\item{ylab}{y axis label.}
-\item{html_id}{manually specify an HTML id for the svg root node. A random one is generated by default.}
+\item{html_id}{manually specify an HTML id for the svg root node. A random
+one is generated by default.}
\item{width}{figure width, computed when displayed}
\item{height}{figure height, computed when displayed}
-\item{legend_width}{legend area width, in pixels. Set to 0 to disable legend completely.}
+\item{legend_width}{legend area width, in pixels. Set to 0 to disable
+legend completely.}
+
+\item{left_margin}{margin on the left of the plot, in pixels}
\item{xlim}{numeric vector of length 2, manual x axis limits}
\item{ylim}{numeric vector of length 2, manual y axis limits}
-\item{dom_id_reset_zoom}{HTML DOM id of the element to bind the "reset zoom" control to.}
+\item{dom_id_reset_zoom}{HTML DOM id of the element to bind the
+"reset zoom" control to.}
+
+\item{dom_id_svg_export}{HTML DOM id of the element to bind the
+"svg export" control to.}
+
+\item{dom_id_lasso_toggle}{HTML DOM id of the element to bind the
+"toggle lasso" control to.}
+
+\item{transitions}{if TRUE, data updates are displayed with smooth
+transitions, if FALSE the whole chart is redrawn. Only used within
+shiny apps.}
+
+\item{menu}{wether to display the tools menu (gear icon)}
+
+\item{lasso}{logical value to add
+{https://github.com/skokenes/D3-Lasso-Plugin}{d3-lasso-plugin} feature}
+
+\item{lasso_callback}{the body of a JavaScript callback function with the
+argument \code{sel} to be applied to a lasso plugin selection}
-\item{dom_id_svg_export}{HTML DOM id of the element to bind the "svg export" control to.}
+\item{click_callback}{the body of a JavaScript callback function whose
+inputs are html_id, and the index of the clicked element.}
-\item{dom_id_lasso_toggle}{HTML DOM id of the element to bind the "toggle lasso" control to.}
+\item{zoom_callback}{the body of a JavaScript callback function whose
+inputs are the new xmin, xmax, ymin and ymax after a zoom action is
+triggered.}
-\item{transitions}{if TRUE, data updates are displayed with smooth transitions, if FALSE the whole chart is redrawn. Only used within shiny apps.}
+\item{lines}{a data frame with at least the \code{slope} and
+\code{intercept} columns, and as many rows as lines to add to
+scatterplot. Style can be added with \code{stroke}, \code{stroke_width}
+and \code{stroke_dasharray} columns. To draw a vertical line, pass
+\code{Inf} as \code{slope} value.}
-\item{lasso}{logical value to add {https://github.com/skokenes/D3-Lasso-Plugin}{d3-lasso-plugin} feature}
+\item{axes_font_size}{font size for axes text (any CSS compatible value)}
-\item{lasso_callback}{the body of a JavaScript callback function with the argument \code{sel} to be applied to a lasso plugin selection}
+\item{legend_font_size}{font size for legend text (any CSS compatible
+value)}
}
\description{
Generates an interactive scatter plot based on d3.js.
@@ -106,7 +167,7 @@ size, point opacity or export the figure as an SVG file via HTML form controls.
Interactive scatter plots based on htmlwidgets and d3.js
}
\examples{
-scatterD3(x = mtcars$wt, y = mtcars$mpg, lab = rownames(mtcars),
+scatterD3(x = mtcars$wt, y = mtcars$mpg, data=NULL, lab = rownames(mtcars),
col_var = mtcars$cyl, symbol_var = mtcars$am,
xlab = "Weight", ylab = "Mpg", col_lab = "Cylinders",
symbol_lab = "Manual transmission", html_id = NULL)
diff --git a/vignettes/introduction.Rmd b/vignettes/introduction.Rmd
index 0ccc163..5354a6d 100644
--- a/vignettes/introduction.Rmd
+++ b/vignettes/introduction.Rmd
@@ -1,5 +1,5 @@
---
-title: "Interactive scatterplots with scatterD3"
+title: "scatterD3 : a Visual Guide"
author: "Julien Barnier"
date: "`r Sys.Date()`"
output:
@@ -7,51 +7,95 @@ output:
fig_width: 5
toc: true
vignette: >
- %\VignetteIndexEntry{Introduction}
+ %\VignetteIndexEntry{scatterD3 : A Visual Guide}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
+```{r, include=FALSE}
+library(scatterD3)
+```
+
+
The `scatterD3` package provides an HTML widget based on the `htmlwidgets` package and allows to produce interactive scatterplots by using the `d3.js` javascript visualization library.
## Basic scatterplot
Starting with the sample `mtcars` dataset, we can produce a basic scatterplot with the following command :
-```{r basic}
+```{r basic, eval=FALSE}
library(scatterD3)
scatterD3(x = mtcars$wt, y = mtcars$mpg)
```
+You can pass data arguments as vectors, like above, but you can also give a data frame as `data` argument and then provide variable names which will be evaluated inside this data frame :
+
+```{r basic_nse}
+scatterD3(data = mtcars , x = wt, y = mpg)
+```
+
+
This will display a simple visualization with the given variables as `x` and `y` axis. There are several interactive features directly available :
- you can zoom in and out with the mouse wheel while the mouse cursor is on the plot
- you can pan the plot by dragging with your mouse
- by hovering over a point, you can display a small tooltip window giving the `x` and `y` values
-You can customize the points size with the `point_size` parameter, their opacity with `point_opacity`, and you can force the plot to have a 1:1 fixed aspect ratio with `fixed = TRUE`.
+You can customize the points size with the `point_size` parameter, their
+global opacity with `point_opacity`, and you can force the plot to have a 1:1
+fixed aspect ratio with `fixed = TRUE`. You can also manually specify the
+points color with the `colors` argument
```{r basic_cust}
-scatterD3(x = mtcars$wt, y = mtcars$mpg, point_size = 15, point_opacity = 0.5, fixed = TRUE)
+scatterD3(data = mtcars, x = wt, y = mpg,
+ point_size = 35, point_opacity = 0.5, fixed = TRUE,
+ colors = "#A94175")
+```
+
+You can change size and opacity of points when hovering with the `hover_size` and `hover_opacity` settings :
+
+```{r hover_cust}
+scatterD3(data = mtcars, x = wt, y = mpg,
+ point_size = 100, point_opacity = 0.5,
+ hover_size = 4, hover_opacity = 1)
```
+## Categorical `x` and `y`
+
+If the `x` or `y` variable is not numeric or is a factor, then an ordinal
+scale is used for the corresponding axis. Note that zooming is then not
+possible along this axis.
+
+```{r categorical}
+mtcars$cyl_fac <- paste(mtcars$cyl, "cylinders")
+scatterD3(data = mtcars, x = cyl_fac, y = mpg)
+```
+
+You can use the `left_margin` argument when using a categorical `y` variable
+if the axis labels are not entirely visible :
+
+```{r categorical_left_margin}
+scatterD3(data = mtcars, x = wt, y = cyl_fac, left_margin = 80)
+```
+
+
## Point labels
You can add text labels to the points by passing a character vector to the `lab` parameter. Labels size are controlled by the `labels_size` parameter.
```{r labels}
-scatterD3(x = mtcars$wt, y = mtcars$mpg, lab = rownames(mtcars), labels_size = 9)
+mtcars$names <- rownames(mtcars)
+scatterD3(data = mtcars, x = wt, y = mpg, lab = names, labels_size = 9)
```
Note that text labels are fully movable : click and drag a label with your mouse to place it where you want. Custom positions are preserved while zooming/panning.
-
-## Mapping colors, symbols and size to variables
+## Mapping colors, symbols, size and opacity to variables
By passing vectors to the `col_var` and/or `symbol_var` arguments, you can map points colors and symbols to other variables.
```{r mapping}
-scatterD3(x = mtcars$wt, y = mtcars$mpg, col_var = mtcars$cyl, symbol_var = mtcars$gear)
+scatterD3(data = mtcars, x = wt, y = mpg, col_var = cyl, symbol_var = gear)
```
A legend is then automatically added. You can manually specify its width with the `legend_width` argument. Use `legend_width = 0` to disable it entirely.
@@ -61,31 +105,101 @@ Note that when hovering over a legend item with your mouse, the corresponding po
You can also map symbol sizes with a variable with the `size_var` argument. `size_range` allows to customize the sizes range :
```{r map_size}
-scatterD3(x = mtcars$wt, y = mtcars$mpg, col_var = mtcars$cyl, size_var = mtcars$hp,
+scatterD3(data = mtcars, x = wt, y = mpg, col_var = cyl, size_var = hp,
size_range = c(10,1000), point_opacity = 0.7)
```
+You can specify custom colors by passing a vector of hexadecimal strings to the `colors` argument. If the vector is named, then the colors will be associated with their names within `col_var`.
+
+```{r map_custom_colors}
+scatterD3(data = mtcars, x = wt, y = mpg, col_var = cyl,
+ colors = c("4" = "#ECD078", "8" = "#C02942", "6" = "#53777A"))
+```
+
+If `col_var` is numeric, not a factor, and has more than 6 unique values, it
+is considered as continuous, and drawn accordingly using the Veridis d3
+interpolator.
+
+```{r map_continuous_color}
+scatterD3(data = mtcars, x = wt, y = mpg, col_var = disp)
+```
+
+In this case, any `colors` argument is ignored. You can force `col_var` to be considered as continuous with `col_continuous = TRUE`.
+
+You can also use the `opacity_var` argument to map point opacity to a variable.
+Note that for now no legend for opacity is added, though.
+
+```{r opacity_var}
+scatterD3(data=mtcars, x=mpg, y=wt, opacity_var = drat)
+```
+
+## Adding lines
+
+In addition to your data points, you can add to your scatterplot. This is done vy passing a *data frame* to the `lines` argument. This *data frame* must have at least two columns called `slope` and `intercept`, and as many rows as lines you want to draw.
+
+For example, if you want to add a 1:1 line :
+
+```{r lines}
+scatterD3(data = mtcars, x = wt, y = mpg, fixed = TRUE,
+ lines = data.frame(slope = 1, intercept = 0))
+```
+
+You can style your lines by adding `stroke`, `stroke_width` and `stroke_dasharray` columns. These columns values will be added as [corresponding styles](https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Fills_and_Strokes) to the generated SVG line. So if you want a wide dashed red horizontal line :
+
+```{r lines_style}
+scatterD3(data = mtcars, x = wt, y = mpg,
+ lines = data.frame(slope = 0,
+ intercept = 30,
+ stroke = "red",
+ stroke_width = 5,
+ stroke_dasharray = "10,5"))
+```
+
+If you want to draw a vertical line, pass the `Inf` value to `slope`. The value of `intercept` is then interpreted as the intercept along the x axis.
+
+By default, if no `lines` argument is provided two dashed horizontal and vertical lines are drawn through the origin, which is equivalent to :
+
+```{r lines_default}
+scatterD3(data = mtcars, x = wt, y = mpg, fixed = TRUE,
+ lines = data.frame(slope = c(0, Inf),
+ intercept = c(0, 0),
+ stroke = "#000",
+ stroke_width = 1,
+ stroke_dasharray = 5))
+```
+
+
## Axis limits
You can manually specify the `x` or `y` axis limits with the `xlim` and `ylim` arguments :
```{r axis_limits}
-scatterD3(x = mtcars$wt, y = mtcars$mpg, xlim=c(0,10), ylim=c(10,35))
+scatterD3(data = mtcars, x = wt, y = mpg, xlim=c(0,10), ylim=c(10,35))
```
+## Axes and legend customization
-## Custom axis and legend labels
-
-You can customize the axis and legend labels with `xlab`, `ylab`, `col_lab`, `symbol_lab` and `size_lab` :
+You can customize the value of the axes and legend labels with `xlab`, `ylab`, `col_lab`, `symbol_lab` and `size_lab` :
```{r cust_labels}
-scatterD3(x = mtcars$wt, y = mtcars$mpg, col_var = mtcars$cyl, symbol_var = mtcars$gear,
+scatterD3(data = mtcars, x = wt, y = mpg, col_var = cyl, symbol_var = gear,
xlab = "Weight", ylab = "Mpg", col_lab = "Cylinders", symbol_lab = "Gears")
```
Note that default tooltips are updated accordingly.
+You can also change the font size of axes and legend text with `axes_font_size` and `legend_font_size` :
+
+```{r cust_labels_size}
+scatterD3(data = mtcars, x = wt, y = mpg, col_var = cyl,
+ xlab = "Weight", ylab = "Mpg",
+ axes_font_size = "120%",
+ legend_font_size = "14px")
+```
+
+You can provide any CSS compatible value, wether a fixed size such as `2em` or a relative one like `95%`.
+
## Custom tooltips
@@ -94,33 +208,100 @@ If the default tooltips don't suit your needs, you can customize them by providi
```{r cust_tooltips}
tooltips <- paste("This is an incredible <strong>", rownames(mtcars),"</strong><br />with ",
mtcars$cyl, "cylinders !")
-scatterD3(x = mtcars$wt, y = mtcars$mpg, tooltip_text = tooltips)
+scatterD3(data = mtcars, x = wt, y = mpg, tooltip_text = tooltips)
```
You can also disable tooltips entirely with `tooltips = FALSE`.
+## Open URLs when clicking points
+
+With the `url_var` argument, you can specify a character vectors of URLs, associated to each point, and which will be opened when the point is clicked.
+
+```{r urls}
+mtcars$urls <- paste0("https://www.duckduckgo.com/?q=", rownames(mtcars))
+scatterD3(data = mtcars, x = wt, y = mpg, lab = names, url_var = urls)
+```
+
+Note that this won't work inside RStudio's internal browser.
+
+## JavaScript callback on clicking point
+
+The optional `click_callback` argument is a character string defining a JavaScript function to be called when a dot is clicked. It must accept two arguments : `html_id` (the unique `id` of the current scatterplot), and `i` (the index of the clicked point).
+
+```{r click_callback}
+scatterD3(data = mtcars, x = wt, y = mpg,
+ click_callback = "function(id, index) {
+ alert('scatterplot ID: ' + id + ' - Point index: ' + index)
+ }")
+```
+
+
+One usage can be to pass the index of the clicked point back to Shiny when `scatterD3` is run inside a Shiny app. The following implementation can do it by using `Shiny.onInputChange()` :
+
+```{r, click_callback_shiny, eval=FALSE}
+scatterD3(data = mtcars, x = wt, y = mpg,
+ click_callback = "function(id, index) {
+ if(id && typeof(Shiny) != 'undefined') {
+ Shiny.onInputChange(id + '_selected', index);
+ }
+}")
+```
+
+Thanks to [detule](https://github.com/detule) and [harveyl888](https://github.com/harveyl888) for the code.
+
+Note that `url_var` and `click_callback` cannot be used at the same time.
+
+
+## JavaScript zoom callback
+
+The optional `zoom_callback` argument is a character string defining a JavaScript function to be called when a zoom event is triggered. It must accept two arguments `xmin`, `xmax`, `ymin` and `ymax` (in this order), which give the new `x` and `y` domains after zooming.
+
+```{r zoom_callback}
+scatterD3(data = mtcars, x = wt, y = mpg,
+ zoom_callback = "function(xmin, xmax, ymin, ymax) {
+ var zoom = '<strong>Zoom</strong><br />xmin = ' + xmin + '<br />xmax = ' + xmax + '<br />ymin = ' + ymin + '<br />ymax = ' + ymax;
+ document.getElementById('zoomExample').innerHTML = zoom;
+ }")
+```
+
+<div id="zoomExample" style="font-size: 80%; background-color: #F9F9F9; padding: 5px; margin-left: 5em; width: 15em;"><strong>Zoom</strong><br /> None yet !</div>
+
+
+
## Confidence ellipses
You can draw a confidence ellipse around the points :
```{r ellipses}
-scatterD3(x = mtcars$wt, y = mtcars$mpg, ellipses = TRUE)
+scatterD3(data = mtcars, x = wt, y = mpg, ellipses = TRUE)
```
Or around the different groups of points defined by `col_var` :
```{r ellipses_col}
-scatterD3(x = mtcars$wt, y = mtcars$mpg, col_var = mtcars$cyl, ellipses = TRUE)
+scatterD3(data = mtcars, x = wt, y = mpg, col_var = cyl, ellipses = TRUE)
```
Ellipses are computed by the `ellipse.default()` function of the [ellipse package](https://cran.r-project.org/package=ellipse). The confidence level can be changed with the `ellipse_level` argument (`0.95` by default).
+## Gear menu
+
+The "gear menu" is a small menu which can be displayed by clocking on the "gear" icon on the top-right corner of the plot. It allows to reset the zoom, export the current graph to SVG, and toggle lasso selection.
+
+It is displayed by default, but you can hide it with the `menu = FALSE` argument.
+
+```{r nomenu}
+scatterD3(data = mtcars, x = wt, y = mpg, menu = FALSE)
+```
+
+
## Lasso selection tool
Thanks to the [d3-lasso-plugin](https://github.com/skokenes/D3-Lasso-Plugin) integration made by @[timelyportfolio](https://github.com/timelyportfolio), you can select and highlight points with a lasso selection tool. To activate it, just add a `lasso = TRUE` argument. The tool is used by shift-clicking and dragging on the plot area (if it doesn't activate, click on the chart first to give it focus).
```{r lasso}
-scatterD3(x = mtcars$wt, y = mtcars$mpg, lab = rownames(mtcars), lasso = TRUE)
+mtcars$names <- rownames(mtcars)
+scatterD3(data = mtcars, x = wt, y = mpg, lab = names, lasso = TRUE)
```
To undo the selection, just shift-click again.
@@ -130,11 +311,38 @@ You can specify a custom JavaScript callback function to be called by passing it
Here is an example which shows an alert with selected point labels :
```{r lasso_callback}
-scatterD3(x = mtcars$wt, y = mtcars$mpg, lab = rownames(mtcars),
+mtcars$names <- rownames(mtcars)
+scatterD3(data = mtcars,
+ x = wt, y = mpg, lab = names,
lasso = TRUE,
lasso_callback = "function(sel) {alert(sel.data().map(function(d) {return d.lab}).join('\\n'));}")
```
+## Custom labels positions export
+
+The "gear menu" allows to export the current custom labels position as a CSV file for later reuse.
+
+For example, if you change the labels placement in the following plot :
+
+```{r labels_export}
+mtcars$names <- rownames(mtcars)
+scatterD3(data = mtcars, x = wt, y = mpg, lab = names)
+```
+
+You can then open the menu and select *Export labels positions* to save them into a CSV file. If you want to use these positions in another plot, you can do something like that :
+
+```{r labels_export_ggplot2, eval = FALSE}
+labels <- read.csv("scatterD3_labels.csv")
+library(ggplot2)
+ggplot() +
+ geom_point(data = mtcars, aes(x=wt, y=mpg)) +
+ geom_text(data = labels,
+ aes(x = scatterD3_label_x,
+ y = scatterD3_label_y,
+ label = scatterD3_label))
+```
+
+
## Other options
@@ -162,14 +370,15 @@ Enabling transitions in your shiny app is quite simple, you just have to add the
### Additional controls : Reset zoom and SVG export
-Furthermore, `scatterD3` provides some additional handlers for two interactive features : SVG export and zoom resetting.
+Furthermore, `scatterD3` provides some additional handlers for three interactive features : SVG export, zoom resetting and lasso selection. Those are already accessible via the "gear menu", but you may want to replace it with custom form controls.
By default, you just have to give the following `id` to the corresponding form controls :
- `#scatterD3-reset-zoom` : reset zoom to default on click
- `#scatterD3-svg-export` : link to download the currently displayed figure as an SVG file
+- `#scatterD3-lasso-toggle` : toggle lasso selection
-If you are not happy with these ids, you can specify their names yourself with the arguments `dom_id_svg_export` and `dom_id_reset_zoom`.
+If you are not happy with these ids, you can specify their names yourself with the arguments `dom_id_svg_export`, `dom_id_reset_zoom` and `dom_id_toggle`.
### Sample app and source code
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/r-cran-scatterd3.git
More information about the debian-med-commit
mailing list