From 59ad5b658b25a8c7c1c2b720fbf23fc44bb579c5 Mon Sep 17 00:00:00 2001 From: Bruceshark Date: Tue, 5 Sep 2023 21:37:12 +0800 Subject: [PATCH 1/5] feat: support pass config file path --- R/gwalkr.R | 8 ++++- man/gwalkr.Rd | 10 +++++- .../src/components/codeExportModal/index.tsx | 33 +++++++++++++++---- 3 files changed, 43 insertions(+), 8 deletions(-) diff --git a/R/gwalkr.R b/R/gwalkr.R index 39757c9..a04bf1e 100644 --- a/R/gwalkr.R +++ b/R/gwalkr.R @@ -17,6 +17,7 @@ #' "age" = list(analyticalType = "measure", semanticType = "quantitative") #' )} #' @param visConfig An optional config string to reproduce your chart. You can copy the string by clicking "export config" button on the GWalkR interface. +#' @param visConfigFile An optional config file path to reproduce your chart. You can download the file by clicking "export config" button then "download" button on the GWalkR interface. #' #' @return An \code{htmlwidget} object that can be rendered in R environments #' @@ -25,12 +26,17 @@ #' gwalkr(mtcars) #' #' @export -gwalkr <- function(data, lang = "en", columnSpecs = list(), visConfig = NULL) { +gwalkr <- function(data, lang = "en", columnSpecs = list(), visConfig = NULL, visConfigFile = NULL) { if (!is.data.frame(data)) stop("data must be a data frame") + if (!is.null(visConfig) && !is.null(visConfigFile)) stop("visConfig and visConfigFile are mutually exclusive") lang <- match.arg(lang, choices = c("en", "ja", "zh")) rawFields <- raw_fields(data, columnSpecs) colnames(data) <- sapply(colnames(data), fname_encode) + + if (!is.null(visConfigFile)) { + visConfig <- readLines(visConfigFile, warn=FALSE) + } # forward options using x x = list( dataSource = jsonlite::toJSON(data), diff --git a/man/gwalkr.Rd b/man/gwalkr.Rd index c4fec10..e88c414 100644 --- a/man/gwalkr.Rd +++ b/man/gwalkr.Rd @@ -4,7 +4,13 @@ \alias{gwalkr} \title{Create GWalkR Interface in "Viewer"} \usage{ -gwalkr(data, lang = "en", columnSpecs = list(), visConfig = NULL) +gwalkr( + data, + lang = "en", + columnSpecs = list(), + visConfig = NULL, + visConfigFile = NULL +) } \arguments{ \item{data}{A data frame to be visualized in the GWalkR. The data frame should not be empty.} @@ -22,6 +28,8 @@ only be one of "measure" or "dimension". \code{semanticType} can only be one of )}} \item{visConfig}{An optional config string to reproduce your chart. You can copy the string by clicking "export config" button on the GWalkR interface.} + +\item{visConfigFile}{An optional config file path to reproduce your chart. You can download the file by clicking "export config" button then "download" button on the GWalkR interface.} } \value{ An \code{htmlwidget} object that can be rendered in R environments diff --git a/web_app/src/components/codeExportModal/index.tsx b/web_app/src/components/codeExportModal/index.tsx index d2f8da9..0838ba7 100644 --- a/web_app/src/components/codeExportModal/index.tsx +++ b/web_app/src/components/codeExportModal/index.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useState } from "react"; import Modal from "../modal"; import { observer } from "mobx-react-lite"; import DefaultButton from "../button/default"; +import PrimaryButton from "../button/primary"; import type { IGlobalStore } from "@kanaries/graphic-walker/dist/store"; @@ -11,6 +12,20 @@ interface ICodeExport { setOpen: (open: boolean) => void; } +const downloadFile = (data: string) => { + const fileName = "config"; + const json = data + const blob = new Blob([json], { type: "application/json" }); + const href = URL.createObjectURL(blob); + const link = document.createElement("a"); + link.href = href; + link.download = fileName + ".json"; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(href); +}; + const CodeExport: React.FC = observer((props) => { const [code, setCode] = useState(""); @@ -31,11 +46,7 @@ const CodeExport: React.FC = observer((props) => {

Config Export

- - visConfig <- '{code}' -
- gwalkr(data="name of your data frame", visConfig=visConfig) -
+ {code}
= observer((props) => { props.setOpen(false); }} /> + { + downloadFile(code); + }} + />{" "} +
+
+
Option 1: paste the config in your R code as a string and pass it to `visConfig` parameter.
+
Option 2: download the config file and pass the file path to `visConfigFile` parameter.
-
Please copy the R code above and paste it into your script.
); From 426a450db1c6fd9933848f31cfea574ef28582d7 Mon Sep 17 00:00:00 2001 From: Bruceshark Date: Tue, 5 Sep 2023 21:40:07 +0800 Subject: [PATCH 2/5] feat: support indication of dark mode --- R/gwalkr.R | 6 ++++-- man/gwalkr.Rd | 3 +++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/R/gwalkr.R b/R/gwalkr.R index a04bf1e..2ff5151 100644 --- a/R/gwalkr.R +++ b/R/gwalkr.R @@ -7,6 +7,7 @@ #' #' @param data A data frame to be visualized in the GWalkR. The data frame should not be empty. #' @param lang A character string specifying the language for the widget. Possible values are "en" (default), "ja", "zh". +#' @param dark A character string specifying the dark mode preference. Possible values are "light" (default), "dark", "media". #' @param columnSpecs An optional list of lists to manually specify the types of some columns in the data frame. #' Each top level element in the list corresponds to a column, and the list assigned to each column should have #' two elements: `analyticalType` and `semanticType`. `analyticalType` can @@ -26,7 +27,7 @@ #' gwalkr(mtcars) #' #' @export -gwalkr <- function(data, lang = "en", columnSpecs = list(), visConfig = NULL, visConfigFile = NULL) { +gwalkr <- function(data, lang = "en", dark = "light", columnSpecs = list(), visConfig = NULL, visConfigFile = NULL) { if (!is.data.frame(data)) stop("data must be a data frame") if (!is.null(visConfig) && !is.null(visConfigFile)) stop("visConfig and visConfigFile are mutually exclusive") lang <- match.arg(lang, choices = c("en", "ja", "zh")) @@ -43,7 +44,8 @@ gwalkr <- function(data, lang = "en", columnSpecs = list(), visConfig = NULL, vi rawFields = rawFields, i18nLang = lang, hideDataSourceConfig = TRUE, - visSpec = visConfig + visSpec = visConfig, + dark = dark ) # create widget diff --git a/man/gwalkr.Rd b/man/gwalkr.Rd index e88c414..79a0b9b 100644 --- a/man/gwalkr.Rd +++ b/man/gwalkr.Rd @@ -7,6 +7,7 @@ gwalkr( data, lang = "en", + dark = "light", columnSpecs = list(), visConfig = NULL, visConfigFile = NULL @@ -17,6 +18,8 @@ gwalkr( \item{lang}{A character string specifying the language for the widget. Possible values are "en" (default), "ja", "zh".} +\item{dark}{A character string specifying the dark mode preference. Possible values are "light" (default), "dark", "media".} + \item{columnSpecs}{An optional list of lists to manually specify the types of some columns in the data frame. Each top level element in the list corresponds to a column, and the list assigned to each column should have two elements: \code{analyticalType} and \code{semanticType}. \code{analyticalType} can From 7a8dbf9d5beda321612b3ff68fd93d816cfc9652 Mon Sep 17 00:00:00 2001 From: Bruceshark Date: Mon, 25 Sep 2023 16:01:07 +0800 Subject: [PATCH 3/5] chore: upgrade graphic walker to 0.4.13 --- web_app/package.json | 2 +- web_app/yarn.lock | 63 ++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 59 insertions(+), 6 deletions(-) diff --git a/web_app/package.json b/web_app/package.json index e150802..971e195 100644 --- a/web_app/package.json +++ b/web_app/package.json @@ -10,7 +10,7 @@ "preview": "vite preview" }, "dependencies": { - "@kanaries/graphic-walker": "^0.4.3", + "@kanaries/graphic-walker": "^0.4.13", "@rollup/plugin-commonjs": "^25.0.2", "@rollup/plugin-replace": "^5.0.2", "@rollup/plugin-terser": "^0.4.3", diff --git a/web_app/yarn.lock b/web_app/yarn.lock index 7034fc0..b208571 100644 --- a/web_app/yarn.lock +++ b/web_app/yarn.lock @@ -200,6 +200,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/runtime@^7.12.5": + version "7.22.15" + resolved "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.22.15.tgz#38f46494ccf6cf020bd4eed7124b425e83e523b8" + integrity sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/runtime@^7.14.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.17.2", "@babel/runtime@^7.19.0", "@babel/runtime@^7.9.2": version "7.22.6" resolved "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.22.6.tgz#57d64b9ae3cff1d67eb067ae117dac087f5bd438" @@ -481,10 +488,10 @@ "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" -"@kanaries/graphic-walker@^0.4.3": - version "0.4.3" - resolved "https://registry.npmmirror.com/@kanaries/graphic-walker/-/graphic-walker-0.4.3.tgz#e30e8a5ef85781433acadeada828726acf68fb27" - integrity sha512-r+c2bipTqlzPGckrYYNDbYmYi8DxBRJrjA4J1uNsckeb0d047ZT9cmQwqawtJPdxDpuxWnkFK7+oFYX6LcSRew== +"@kanaries/graphic-walker@^0.4.13": + version "0.4.13" + resolved "https://registry.npmmirror.com/@kanaries/graphic-walker/-/graphic-walker-0.4.13.tgz#a03d400f4774974454e1525a2e3a3d879e31eac6" + integrity sha512-8Mn8rQjAuGgnEDN60VgxAlV1aVMqjJ/UdbRdtQkaxwPKdSOQ+DTMoFu/uccFQUaotT77eWJMLIvc06l+R+mpRw== dependencies: "@headlessui/react" "^1.7.12" "@heroicons/react" "^2.0.8" @@ -492,6 +499,7 @@ "@kanaries/web-data-loader" "^0.1.7" "@tailwindcss/forms" "^0.5.4" autoprefixer "^10.3.5" + canvas-size "^1.2.6" d3-format "^3.1.0" d3-scale "^4.0.2" d3-time-format "^4.1.0" @@ -505,6 +513,8 @@ postcss "^8.3.7" postinstall-postinstall "^2.1.0" re-resizable "^6.9.8" + react-dropzone "^14.2.3" + react-error-boundary "^4.0.11" react-i18next "^11.18.6" react-leaflet "^4.2.1" react-shadow "^20.0.0" @@ -872,6 +882,11 @@ array-union@^2.1.0: resolved "https://registry.npmmirror.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== +attr-accept@^2.2.2: + version "2.2.2" + resolved "https://registry.npmmirror.com/attr-accept/-/attr-accept-2.2.2.tgz#646613809660110749e92f2c10833b70968d929b" + integrity sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg== + autoprefixer@^10.3.5, autoprefixer@^10.4.14: version "10.4.14" resolved "https://registry.npmmirror.com/autoprefixer/-/autoprefixer-10.4.14.tgz#e28d49902f8e759dd25b153264e862df2705f79d" @@ -962,6 +977,11 @@ caniuse-lite@^1.0.30001464, caniuse-lite@^1.0.30001503: resolved "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001514.tgz#e2a7e184a23affc9367b7c8d734e7ec4628c1309" integrity sha512-ENcIpYBmwAAOm/V2cXgM7rZUrKKaqisZl4ZAI520FIkqGXUxJjmaIssbRW5HVVR5tyV6ygTLIm15aU8LUmQSaQ== +canvas-size@^1.2.6: + version "1.2.6" + resolved "https://registry.npmmirror.com/canvas-size/-/canvas-size-1.2.6.tgz#1eaa6b56167cf2a70fa4021680829d2073b45706" + integrity sha512-x2iVHOrZ5x9V0Hwx6kBz+Yxf/VCAII+jrD6WLjJbytJLozHq/oDJjEva432Os0eHxWMFR0vYlLJwTr6QxyxQqw== + chalk@^2.0.0: version "2.4.2" resolved "https://registry.npmmirror.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -1496,6 +1516,13 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" +file-selector@^0.6.0: + version "0.6.0" + resolved "https://registry.npmmirror.com/file-selector/-/file-selector-0.6.0.tgz#fa0a8d9007b829504db4d07dd4de0310b65287dc" + integrity sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw== + dependencies: + tslib "^2.4.0" + fill-range@^7.0.1: version "7.0.1" resolved "https://registry.npmmirror.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" @@ -2168,7 +2195,7 @@ prelude-ls@^1.2.1: resolved "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== -prop-types@^15.7.2: +prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.npmmirror.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -2212,6 +2239,22 @@ react-dom@^18.2.0: loose-envify "^1.1.0" scheduler "^0.23.0" +react-dropzone@^14.2.3: + version "14.2.3" + resolved "https://registry.npmmirror.com/react-dropzone/-/react-dropzone-14.2.3.tgz#0acab68308fda2d54d1273a1e626264e13d4e84b" + integrity sha512-O3om8I+PkFKbxCukfIR3QAGftYXDZfOE2N1mr/7qebQJHs7U+/RSL/9xomJNpRg9kM5h9soQSdf0Gc7OHF5Fug== + dependencies: + attr-accept "^2.2.2" + file-selector "^0.6.0" + prop-types "^15.8.1" + +react-error-boundary@^4.0.11: + version "4.0.11" + resolved "https://registry.npmmirror.com/react-error-boundary/-/react-error-boundary-4.0.11.tgz#36bf44de7746714725a814630282fee83a7c9a1c" + integrity sha512-U13ul67aP5DOSPNSCWQ/eO0AQEYzEFkVljULQIjMV0KlffTAhxuDoBKdO0pb/JZ8mDhMKFZ9NZi0BmLGUiNphw== + dependencies: + "@babel/runtime" "^7.12.5" + react-i18next@^11.18.6: version "11.18.6" resolved "https://registry.npmmirror.com/react-i18next/-/react-i18next-11.18.6.tgz#e159c2960c718c1314f1e8fcaa282d1c8b167887" @@ -2294,6 +2337,11 @@ regenerator-runtime@^0.13.11: resolved "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== +regenerator-runtime@^0.14.0: + version "0.14.0" + resolved "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45" + integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA== + require-directory@^2.1.1: version "2.1.1" resolved "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -2638,6 +2686,11 @@ tslib@^2.1.0, tslib@^2.5.0: resolved "https://registry.npmmirror.com/tslib/-/tslib-2.6.0.tgz#b295854684dbda164e181d259a22cd779dcd7bc3" integrity sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA== +tslib@^2.4.0: + version "2.6.2" + resolved "https://registry.npmmirror.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== + tslib@~2.5.0: version "2.5.3" resolved "https://registry.npmmirror.com/tslib/-/tslib-2.5.3.tgz#24944ba2d990940e6e982c4bea147aba80209913" From 5fa8d12980a41c55963b5461e804c5ef4a0e96f1 Mon Sep 17 00:00:00 2001 From: Bruceshark Date: Mon, 25 Sep 2023 16:01:32 +0800 Subject: [PATCH 4/5] bump version to 0.1.3 --- DESCRIPTION | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index b8c9d37..f9d0434 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,11 +1,11 @@ Package: GWalkR Title: Interactive Exploratory Data Analysis Tool -Version: 0.1.2 +Version: 0.1.3 Authors@R: c( - person("Yue", "Yu", , "yyubv@connect.ust.hk", role = c("aut", "cre"), + person("Yue", "Yu", , "yue.yu@connect.ust.hk", role = c("aut", "cre"), comment = c(ORCID = "0000-0002-9302-0793")), person("Kanaries Data Inc.", role = c("cph", "fnd"))) -Maintainer: Yue Yu +Maintainer: Yue Yu Description: Simplify your R data analysis and data visualization workflow by turning your data frame into an interactive 'Tableau'-like interface, leveraging the 'graphic-walker' JavaScript library and the 'htmlwidgets' package. License: Apache License (>= 2) Encoding: UTF-8 From 56ff8057347979d8b236632bf4157561d05860f1 Mon Sep 17 00:00:00 2001 From: Bruceshark Date: Mon, 25 Sep 2023 20:05:48 +0800 Subject: [PATCH 5/5] fix: code style --- web_app/src/components/codeExportModal/index.tsx | 2 +- web_app/src/index.tsx | 16 ++++++++-------- web_app/src/interfaces/index.ts | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/web_app/src/components/codeExportModal/index.tsx b/web_app/src/components/codeExportModal/index.tsx index 0838ba7..508124c 100644 --- a/web_app/src/components/codeExportModal/index.tsx +++ b/web_app/src/components/codeExportModal/index.tsx @@ -14,7 +14,7 @@ interface ICodeExport { const downloadFile = (data: string) => { const fileName = "config"; - const json = data + const json = data; const blob = new Blob([json], { type: "application/json" }); const href = URL.createObjectURL(blob); const link = document.createElement("a"); diff --git a/web_app/src/index.tsx b/web_app/src/index.tsx index 42cd7a7..189c12d 100644 --- a/web_app/src/index.tsx +++ b/web_app/src/index.tsx @@ -8,8 +8,8 @@ import type { IDataSetInfo, IMutField, IRow, IVisSpec } from "@kanaries/graphic- import type { IStoInfo } from "@kanaries/graphic-walker/dist/utils/save"; import { getExportTool } from "./tools/exportTool"; import CodeExportModal from "./components/codeExportModal"; -import { StyleSheetManager } from 'styled-components'; -import tailwindStyle from 'tailwindcss/tailwind.css?inline' +import { StyleSheetManager } from "styled-components"; +import tailwindStyle from "tailwindcss/tailwind.css?inline"; // eslint-disable-next-line @typescript-eslint/no-unused-vars const App: React.FC = observer((propsIn) => { @@ -39,7 +39,7 @@ const App: React.FC = observer((propsIn) => { }, ], specList, - } as IStoInfo) + } as IStoInfo); }, 1); } else { storeRef?.current?.commonStore?.updateTempSTDDS({ @@ -49,10 +49,10 @@ const App: React.FC = observer((propsIn) => { } as IDataSetInfo); storeRef?.current?.commonStore?.commitTempDS(); } - } + }; React.useEffect(() => { - setData(dataSource, props.rawFields) + setData(dataSource, props.rawFields); }, []); const exportTool = getExportTool(setExportOpen); @@ -66,7 +66,7 @@ const App: React.FC = observer((propsIn) => { return (
- {/*
*/} + {/*
*/}
@@ -77,10 +77,10 @@ const App: React.FC = observer((propsIn) => { const GWalker = (props: IAppProps, id: string) => { const container = document.getElementById(id); if (container) { - const shadowRoot = container.attachShadow({ mode: 'open' }); + const shadowRoot = container.attachShadow({ mode: "open" }); // Add Tailwind CSS to the shadow root - const styleElement = document.createElement('style'); + const styleElement = document.createElement("style"); styleElement.textContent = tailwindStyle; shadowRoot.appendChild(styleElement); diff --git a/web_app/src/interfaces/index.ts b/web_app/src/interfaces/index.ts index 2b041e1..f5addd4 100644 --- a/web_app/src/interfaces/index.ts +++ b/web_app/src/interfaces/index.ts @@ -1,4 +1,4 @@ -import type { IGWProps } from '@kanaries/graphic-walker/dist/App' +import type { IGWProps } from "@kanaries/graphic-walker/dist/App"; export interface IAppProps extends IGWProps { id: string; @@ -8,4 +8,4 @@ export interface IAppProps extends IGWProps { env?: string; needLoadDatas?: boolean; specType?: string; -} \ No newline at end of file +}