diff --git a/.circleci/config.yml b/.circleci/config.yml
index 78b0681b2..37f3b162b 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -25,9 +25,10 @@ jobs:
python -m venv venv || virtualenv venv
. venv/bin/activate
pip install -r dev-requirements.txt --quiet
- git clone --depth 1 git@github.com:plotly/dash.git dash-main
- pip install -e ./dash-main[dev,testing] --quiet
- cd dash-main/dash-renderer && npm ci && npm run build && pip install -e . && cd ./../..
+ git clone --depth 1 -b dcg-template git@github.com:plotly/dash.git main
+ cd ./main/\@plotly/dash-generate-components; npm ci; cd ../../..
+ pip install -e ./main[dev,testing] --quiet
+ cd main/dash-renderer && npm ci && npm run build && pip install -e . && cd ./../..
- run:
name: Build
@@ -35,8 +36,9 @@ jobs:
. venv/bin/activate
npm run private::build:js-test
npm run private::build:backends
- python setup.py sdist
- cd dist
+ ls -laR .dcg/dist/py
+ cat .dcg/dist/py/dash_table/metadata.json
+ cd .dcg/dist/py; python setup.py sdist; cd dist
find . -name "*.gz" | xargs pip install --no-cache-dir --ignore-installed && cd ..
- run:
@@ -107,7 +109,9 @@ jobs:
python -m venv venv || virtualenv venv
. venv/bin/activate
pip install -r dev-requirements.txt --quiet
- pip install --progress-bar off -e git+https://github.com/plotly/dash.git@dev#egg=dash[dev,testing]
+ pip install --progress-bar off -e git+https://github.com/plotly/dash.git@dcg-template#egg=dash[dev,testing]
+ git clone --depth 1 -b dcg-template git@github.com:plotly/dash.git main
+ cd ./main/\@plotly/dash-generate-components; npm ci; cd ../../..
- run:
name: Run tests
@@ -213,15 +217,17 @@ jobs:
name: Install dependencies (dash)
command: |
. venv/bin/activate
- git clone --depth 1 git@github.com:plotly/dash.git dash-main
- pip install -e ./dash-main[dev,testing] --quiet
- cd dash-main/dash-renderer && npm ci && npm run build && pip install -e . && cd ../..
+ git clone --depth 1 -b dcg-template git@github.com:plotly/dash.git main
+ cd ./main/\@plotly/dash-generate-components; npm ci; cd ../../..
+ pip install -e ./main[dev,testing] --quiet
+ cd main/dash-renderer && npm ci && npm run build && pip install -e . && cd ../..
- run:
name: Install test requirements
command: |
. venv/bin/activate
npm run build
+ cd .dcg/dist/py
python setup.py sdist
cd dist
find . -name "*.gz" | xargs pip install --no-cache-dir --ignore-installed && cd ..
diff --git a/.dcg/R/dashDataTable.R b/.dcg/R/dashDataTable.R
new file mode 100644
index 000000000..4c288f18b
--- /dev/null
+++ b/.dcg/R/dashDataTable.R
@@ -0,0 +1,70 @@
+# For comprehensive documentation of this package's features,
+# please consult https://dashr.plot.ly/datatable
+#
+# A package vignette is currently in development and will
+# provide many of the same examples currently available online
+# in an offline-friendly format.
+
+# The following if statement is not required to run this
+# example locally, but was added at the request of CRAN
+# maintainers.
+if (interactive() && require(dash)) {
+ library(dash)
+ library(dashTable)
+
+ app <- Dash$new()
+
+ # We can easily restrict the number of rows to display at
+ # once by using style_table:
+ app$layout(
+ dashDataTable(
+ id = "table",
+ columns = lapply(colnames(iris),
+ function(colName){
+ list(
+ id = colName,
+ name = colName
+ )
+ }),
+ style_table = list(
+ maxHeight = "250px",
+ overflowY = "scroll"
+ ),
+ data = df_to_list(iris)
+ )
+ )
+
+ app$run_server()
+
+ app <- Dash$new()
+
+ # We can also make rows and columns selectable/deletable
+ # by setting a few additional attributes:
+ app$layout(
+ dashDataTable(
+ id = "table",
+ columns = lapply(colnames(iris),
+ function(colName){
+ list(
+ id = colName,
+ name = colName,
+ deletable = TRUE
+ )
+ }),
+ style_table = list(
+ maxHeight = "250px",
+ overflowY = "scroll"
+ ),
+ data = df_to_list(iris),
+ editable = TRUE,
+ filter_action = "native",
+ sort_action = "native",
+ sort_mode = "multi",
+ column_selectable = "single",
+ row_selectable = "multi",
+ row_deletable = TRUE
+ )
+ )
+
+ app$run_server()
+}
\ No newline at end of file
diff --git a/.dcg/R/df_to_list.R b/.dcg/R/df_to_list.R
new file mode 100644
index 000000000..ac4de8ae7
--- /dev/null
+++ b/.dcg/R/df_to_list.R
@@ -0,0 +1,8 @@
+df_to_list <- function(df) {
+ if(!(is.data.frame(df)))
+ stop("df_to_list requires a data.frame object; please verify that df is of the correct type.")
+ stats::setNames(lapply(split(df, seq(nrow(df))),
+ FUN = function (x) {
+ as.list(x)
+ }), NULL)
+}
diff --git a/.dcg/R/df_to_list.Rd b/.dcg/R/df_to_list.Rd
new file mode 100644
index 000000000..782bf6524
--- /dev/null
+++ b/.dcg/R/df_to_list.Rd
@@ -0,0 +1,43 @@
+% Auto-generated: do not edit by hand
+\name{df_to_list}
+
+\alias{df_to_list}
+
+\title{Convert data.frame objects to list-of-lists format}
+
+\description{
+Convert a \code{\link{data.frame}} to a list of lists for compatibility with \code{\link{dashDataTable}}. The function will return a nested list object in which the sublists contain named elements of varying type; the names correspond to the column names in the original \code{\link{data.frame}}.
+}
+
+\usage{
+df_to_list(df)
+}
+
+\arguments{
+\item{df}{A \code{data.frame} object, which will be transformed into a list of lists. Each row will become a single named list, in which the elements are named as the columns from which they were extracted.}
+}
+
+\value{
+a \code{list} object, in which the sublists are named elements of varying type; the names correspond to the column names in the \code{data.frame} specified by \code{df}.
+}
+
+\examples{
+# first, create data frame
+df <- read.csv(url(
+ 'https://raw.githubusercontent.com/plotly/datasets/master/solar.csv'
+ ),
+ check.names=FALSE,
+ stringsAsFactors=FALSE
+)
+
+# then convert to list-of-lists format for use in dashTable
+# the following snippet below will print as JSON
+# see the help for dashDataTable to see an actual app example
+dashDataTable(
+ id = 'table',
+ columns = lapply(colnames(df), function(x) {
+ list(name = x, id = x)
+ }),
+ data = df_to_list(df)
+)
+}
diff --git a/.dcg/R/pkg_help_description.txt b/.dcg/R/pkg_help_description.txt
new file mode 100644
index 000000000..02bc1198b
--- /dev/null
+++ b/.dcg/R/pkg_help_description.txt
@@ -0,0 +1,7 @@
+An interactive table component designed for editing and exploring
+large datasets, 'dashDataTable' is rendered with standard, semantic HTML
+
markup, which makes it accessible, responsive, and easy
+to style. This component was written from scratch in 'React.js'
+specifically for the 'Dash' community. Its API was designed to be
+ergonomic and its behaviour is completely customizable through its
+properties.
\ No newline at end of file
diff --git a/.dcg/config.yaml b/.dcg/config.yaml
new file mode 100644
index 000000000..828ef8e83
--- /dev/null
+++ b/.dcg/config.yaml
@@ -0,0 +1,68 @@
+componentPaths:
+ - src/dash-table/dash/DataTable.js
+
+recipes:
+ - py
+ - R
+
+dist:
+ - source: dash_table/bundle.js
+ target: bundle.js
+ external: true
+ - source: dash_table/bundle.js.map
+ target: bundle.js.map
+ dynamic: true
+ external: true
+ - source: dash_table/async-export.js
+ target: async-export.js
+ async: true
+ external: true
+ - source: dash_table/async-export.js.map
+ target: async-export.js.map
+ dynamic: true
+ external: true
+ - source: dash_table/async-highlight.js
+ target: async-highlight.js
+ async: true
+ external: true
+ - source: dash_table/async-highlight.js.map
+ target: async-highlight.js.map
+ dynamic: true
+ external: true
+ - source: dash_table/async-table.js
+ target: async-table.js
+ async: true
+ external: true
+ - source: dash_table/async-table.js.map
+ target: async-table.js.map
+ dynamic: true
+ external: true
+
+vars:
+ py:
+ dist:
+ - source: Format.py
+ target: Format.py
+ - source: FormatTemplate.py
+ target: FormatTemplate.py
+
+ R:
+ prefix: dash
+ depends:
+ imports:
+ suggests:
+ - dash
+
+ pkg_authors: c(person("Chris", "Parmer", email = "chris@plotly.com", role = c("aut")), person("Ryan Patrick", "Kyle", email = "ryan@plotly.com", role = c("cre"), comment = c(ORCID = "0000-0002-4958-2844")), person(family = "Plotly Technologies, Inc.", role = "cph"))
+ pkg_help_description: ${js.core.readConfigFile('pkg_help_description.txt')}
+ pkg_help_title: Core Interactive Table Component for 'Dash'
+ pkg_copyright: Plotly Technologies, Inc.
+ examples:
+ DataTable:
+ dontrun: false
+ code: ${js.core.readConfigFile('dashDataTable.R')}
+ dist:
+ - source: df_to_list.Rd
+ target: man/df_to_list.Rd
+ - source: df_to_list.R
+ target: R/df_to_list.R
\ No newline at end of file
diff --git a/.dcg/py/Format.py b/.dcg/py/Format.py
new file mode 100644
index 000000000..8dd12638c
--- /dev/null
+++ b/.dcg/py/Format.py
@@ -0,0 +1,287 @@
+import collections
+
+
+def get_named_tuple(name, dict):
+ return collections.namedtuple(name, dict.keys())(*dict.values())
+
+
+Align = get_named_tuple(
+ "align",
+ {"default": "", "left": "<", "right": ">", "center": "^", "right_sign": "="},
+)
+
+Group = get_named_tuple("group", {"no": "", "yes": ","})
+
+Padding = get_named_tuple("padding", {"no": "", "yes": "0"})
+
+Prefix = get_named_tuple(
+ "prefix",
+ {
+ "yocto": 10 ** -24,
+ "zepto": 10 ** -21,
+ "atto": 10 ** -18,
+ "femto": 10 ** -15,
+ "pico": 10 ** -12,
+ "nano": 10 ** -9,
+ "micro": 10 ** -6,
+ "milli": 10 ** -3,
+ "none": None,
+ "kilo": 10 ** 3,
+ "mega": 10 ** 6,
+ "giga": 10 ** 9,
+ "tera": 10 ** 12,
+ "peta": 10 ** 15,
+ "exa": 10 ** 18,
+ "zetta": 10 ** 21,
+ "yotta": 10 ** 24,
+ },
+)
+
+Scheme = get_named_tuple(
+ "scheme",
+ {
+ "default": "",
+ "decimal": "r",
+ "decimal_integer": "d",
+ "decimal_or_exponent": "g",
+ "decimal_si_prefix": "s",
+ "exponent": "e",
+ "fixed": "f",
+ "percentage": "%",
+ "percentage_rounded": "p",
+ "binary": "b",
+ "octal": "o",
+ "lower_case_hex": "x",
+ "upper_case_hex": "X",
+ "unicode": "c",
+ },
+)
+
+Sign = get_named_tuple(
+ "sign",
+ {"default": "", "negative": "-", "positive": "+", "parantheses": "(", "space": " "},
+)
+
+Symbol = get_named_tuple(
+ "symbol", {"no": "", "yes": "$", "binary": "#b", "octal": "#o", "hex": "#x"}
+)
+
+Trim = get_named_tuple("trim", {"no": "", "yes": "~"})
+
+
+class Format:
+ def __init__(self, **kwargs):
+ self._locale = {}
+ self._nully = ""
+ self._prefix = Prefix.none
+ self._specifier = {
+ "align": Align.default,
+ "fill": "",
+ "group": Group.no,
+ "width": "",
+ "padding": Padding.no,
+ "precision": "",
+ "sign": Sign.default,
+ "symbol": Symbol.no,
+ "trim": Trim.no,
+ "type": Scheme.default,
+ }
+
+ valid_methods = [
+ m for m in dir(self.__class__) if m[0] != "_" and m != "to_plotly_json"
+ ]
+
+ for kw, val in kwargs.items():
+ if kw not in valid_methods:
+ raise TypeError(
+ "{0} is not a format method. Expected one of".format(kw),
+ str(list(valid_methods)),
+ )
+
+ getattr(self, kw)(val)
+
+ def _validate_char(self, value):
+ self._validate_string(value)
+
+ if len(value) != 1:
+ raise ValueError("expected value to a string of length one")
+
+ def _validate_non_negative_integer_or_none(self, value):
+ if value is None:
+ return
+
+ if not isinstance(value, int):
+ raise TypeError("expected value to be an integer")
+
+ if value < 0:
+ raise ValueError("expected value to be non-negative", str(value))
+
+ def _validate_named(self, value, named_values):
+ if value not in named_values:
+ raise TypeError("expected value to be one of", str(list(named_values)))
+
+ def _validate_string(self, value):
+ if not isinstance(value, (str, u"".__class__)):
+ raise TypeError("expected value to be a string")
+
+ # Specifier
+ def align(self, value):
+ self._validate_named(value, Align)
+
+ self._specifier["align"] = value
+ return self
+
+ def fill(self, value):
+ self._validate_char(value)
+
+ self._specifier["fill"] = value
+ return self
+
+ def group(self, value):
+ if isinstance(value, bool):
+ value = Group.yes if value else Group.no
+
+ self._validate_named(value, Group)
+
+ self._specifier["group"] = value
+ return self
+
+ def padding(self, value):
+ if isinstance(value, bool):
+ value = Padding.yes if value else Padding.no
+
+ self._validate_named(value, Padding)
+
+ self._specifier["padding"] = value
+ return self
+
+ def padding_width(self, value):
+ self._validate_non_negative_integer_or_none(value)
+
+ self._specifier["width"] = value if value is not None else ""
+ return self
+
+ def precision(self, value):
+ self._validate_non_negative_integer_or_none(value)
+
+ self._specifier["precision"] = ".{0}".format(value) if value is not None else ""
+ return self
+
+ def scheme(self, value):
+ self._validate_named(value, Scheme)
+
+ self._specifier["type"] = value
+ return self
+
+ def sign(self, value):
+ self._validate_named(value, Sign)
+
+ self._specifier["sign"] = value
+ return self
+
+ def symbol(self, value):
+ self._validate_named(value, Symbol)
+
+ self._specifier["symbol"] = value
+ return self
+
+ def trim(self, value):
+ if isinstance(value, bool):
+ value = Trim.yes if value else Trim.no
+
+ self._validate_named(value, Trim)
+
+ self._specifier["trim"] = value
+ return self
+
+ # Locale
+ def symbol_prefix(self, value):
+ self._validate_string(value)
+
+ if "symbol" not in self._locale:
+ self._locale["symbol"] = [value, ""]
+ else:
+ self._locale["symbol"][0] = value
+
+ return self
+
+ def symbol_suffix(self, value):
+ self._validate_string(value)
+
+ if "symbol" not in self._locale:
+ self._locale["symbol"] = ["", value]
+ else:
+ self._locale["symbol"][1] = value
+
+ return self
+
+ def decimal_delimiter(self, value):
+ self._validate_char(value)
+
+ self._locale["decimal"] = value
+ return self
+
+ def group_delimiter(self, value):
+ self._validate_char(value)
+
+ self._locale["group"] = value
+ return self
+
+ def groups(self, groups):
+ groups = (
+ groups
+ if isinstance(groups, list)
+ else [groups]
+ if isinstance(groups, int)
+ else None
+ )
+
+ if not isinstance(groups, list):
+ raise TypeError("expected groups to be an integer or a list of integers")
+ if len(groups) == 0:
+ raise ValueError(
+ "expected groups to be an integer or a list of " "one or more integers"
+ )
+
+ for group in groups:
+ if not isinstance(group, int):
+ raise TypeError("expected entry to be an integer")
+
+ if group <= 0:
+ raise ValueError("expected entry to be a non-negative integer")
+
+ self._locale["grouping"] = groups
+ return self
+
+ # Nully
+ def nully(self, value):
+ self._nully = value
+ return self
+
+ # Prefix
+ def si_prefix(self, value):
+ self._validate_named(value, Prefix)
+
+ self._prefix = value
+ return self
+
+ def to_plotly_json(self):
+ f = {}
+ f["locale"] = self._locale.copy()
+ f["nully"] = self._nully
+ f["prefix"] = self._prefix
+ aligned = self._specifier["align"] != Align.default
+ f["specifier"] = "{}{}{}{}{}{}{}{}{}{}".format(
+ self._specifier["fill"] if aligned else "",
+ self._specifier["align"],
+ self._specifier["sign"],
+ self._specifier["symbol"],
+ self._specifier["padding"],
+ self._specifier["width"],
+ self._specifier["group"],
+ self._specifier["precision"],
+ self._specifier["trim"],
+ self._specifier["type"],
+ )
+
+ return f
diff --git a/.dcg/py/FormatTemplate.py b/.dcg/py/FormatTemplate.py
new file mode 100644
index 000000000..9c2688ca8
--- /dev/null
+++ b/.dcg/py/FormatTemplate.py
@@ -0,0 +1,19 @@
+from .Format import Format, Group, Scheme, Sign, Symbol
+
+
+def money(decimals, sign=Sign.default):
+ return Format(
+ group=Group.yes,
+ precision=decimals,
+ scheme=Scheme.fixed,
+ sign=sign,
+ symbol=Symbol.yes,
+ )
+
+
+def percentage(decimals, rounded=False):
+ if not isinstance(rounded, bool):
+ raise TypeError("expected rounded to be a boolean")
+
+ rounded = Scheme.percentage_rounded if rounded else Scheme.percentage
+ return Format(scheme=rounded, precision=decimals)
diff --git a/package.json b/package.json
index bab3cfb20..6475c3afd 100644
--- a/package.json
+++ b/package.json
@@ -21,14 +21,14 @@
"private::build:js-test": "run-s \"private::build -- --mode development --config webpack.test.config.js\"",
"private::build:js-test-standalone": "run-s \"private::build -- --mode development --config webpack.test.standalone.config.js\"",
"private::build:js-test-watch": "run-s \"private::build -- --mode development --config webpack.test.config.js --watch\"",
- "private::build:backends": "dash-generate-components src/dash-table/dash/DataTable.js dash_table -p package-info.json && cp dash_table_base/** dash_table/ && dash-generate-components src/dash-table/dash/DataTable.js dash_table -p package-info.json --r-prefix 'dash' --r-suggests 'dash' --jl-prefix 'dash'",
+ "private::build:backends": "node ./main/\\@plotly/dash-generate-components/src/run.js -c .dcg/config.yaml",
"private::format.ts": "npm run private::lint.ts -- --fix",
"private::format.prettier": "prettier --config .prettierrc --write \"src/**/*.{js,ts,tsx}\"",
- "private::format.black": "black --exclude dash_table .",
+ "private::format.black": "black --exclude dash_table .dcg .",
"private::host_js": "http-server ./dash_table -c-1 --silent",
"private::lint.ts": "tslint --project tsconfig.json --config tslint.json",
- "private::lint.flake": "flake8 --exclude=dash_table,node_modules,venv",
- "private::lint.black": "black --check --exclude dash_table .",
+ "private::lint.flake": "flake8 --exclude=dash_table,node_modules,venv,.dcg",
+ "private::lint.black": "black --check --exclude dash_table .dcg .",
"private::lint.prettier": "prettier --config .prettierrc \"src/**/*.{js,ts,tsx}\" --list-different",
"private::wait_js": "wait-on http://localhost:8080",
"private::opentests": "cypress open",