From 700410b54018a0a317891fa9f6739c50f8d05339 Mon Sep 17 00:00:00 2001 From: Birm Date: Tue, 23 Apr 2024 13:00:18 -0400 Subject: [PATCH 1/7] start drat --- .github/workflows/draft-paper.yml | 23 +++++++++++++ paper.bib | 57 +++++++++++++++++++++++++++++++ paper.md | 48 ++++++++++++++++++++++++++ 3 files changed, 128 insertions(+) create mode 100644 .github/workflows/draft-paper.yml create mode 100644 paper.bib create mode 100644 paper.md diff --git a/.github/workflows/draft-paper.yml b/.github/workflows/draft-paper.yml new file mode 100644 index 0000000..5efa396 --- /dev/null +++ b/.github/workflows/draft-paper.yml @@ -0,0 +1,23 @@ +on: [push] + +jobs: + paper: + runs-on: ubuntu-latest + name: JOSS Paper Draft + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Build draft PDF + uses: openjournals/openjournals-draft-action@master + with: + journal: joss + # This should be the path to the paper within your repo. + paper-path: paper.md + - name: Upload + uses: actions/upload-artifact@v1 + with: + name: paper + # This is the output path where Pandoc will write the compiled + # PDF. Note, this should be the same directory as the input + # paper.md + path: paper.pdf \ No newline at end of file diff --git a/paper.bib b/paper.bib new file mode 100644 index 0000000..639f18e --- /dev/null +++ b/paper.bib @@ -0,0 +1,57 @@ +@online{tableau, + author = {Tableau Software}, + title = {Tableau}, + year = {2024}, + url = {https://www.tableau.com/} +} + +@misc{bokeh, + author = {Bokeh Contributors}, + title = {Bokeh: an interactive visualization library for modern web browsers}, + year = {2024}, + url = {https://github.com/bokeh/bokeh} +} + +@article{cbioportal2013, + author = {Gao, Jianjiong and Aksoy, Bulent Arman and Dogrusoz, Ugur and Dresdner, Gideon and Gross, Benjamin and Sumer, S Onur and Sun, Yichao and Jacobsen, Anders and Sinha, Rileen and Larsson, Erik and Cerami, Ethan and Sander, Chris and Schultz, Nikolaus}, + title = {Integrative analysis of complex cancer genomics and clinical profiles using the cBioPortal}, + journal = {Sci Signal}, + year = {2013}, + volume = {6}, + number = {269} +} + +@inproceedings{datascope2017, + author = {Iyer, Ganesh and DuttaDuwarah, Sapoonjyoti and Sharma, Ashish}, + title = {DataScope: Interactive visual exploratory dashboards for large multidimensional data}, + booktitle = {2017 IEEE Workshop on Visual Analytics in Healthcare (VAHC)}, + year = {2017}, + pages = {17-23} +} + +@article{nbia2020, + author = {Nguyen, Tin and Shafi, Adib and Nguyen, Tuan-Minh and Schissler, A. Grant and Draghici, Sorin}, + title = {NBIA: a network-based integrative analysis framework - applied to pathway analysis}, + journal = {Scientific Reports}, + year = {2020}, + volume = {10}, + number = {1} +} + +@article{tica2013, + author = {Clark, Kenneth and Vendt, Bruce and Smith, Kirk and others}, + title = {The Cancer Imaging Archive (TCIA): Maintaining and Operating a Public Information Repository}, + journal = {J Digit Imaging}, + year = {2013}, + volume = {26}, + number = {6}, + pages = {1045-1057} +} + +@article{prism2020, + author = {Sharma, Ashish and Tarbox, Lawrence and Kurc, Tahsin and Bona, Jonathan and Smith, Kirk and Kathiravelu, Pradeeban and Bremer, Erich and Saltz, Joel H. and Prior, Fred}, + title = {PRISM: A Platform for Imaging in Precision Medicine}, + journal = {JCO Clinical Cancer Informatics}, + year = {2020}, + volume = {4} +} diff --git a/paper.md b/paper.md new file mode 100644 index 0000000..5f185eb --- /dev/null +++ b/paper.md @@ -0,0 +1,48 @@ +--- +title: 'Eaglescope: an interactive visualization and cohort selection tool designed for biomedical data exploration.' +tags: + - javascript + - interactive visualization + - data exploration + - biomedical research + - data analysis +authors: + - name: Nan Li + equal-contrib: true + affiliation: 1 + - name: Ryan Birmingham + orcid: 0000-0002-7943-6346 + equal-contrib: true + affiliation: 1 + - name: Tony Pan + affiliation: 1 + - name: Yahia Zakaria + corresponding: true + affiliation: 2 +affiliations: + - name: Emory Univeristy, USA + index: 1 + - name: Independent Researcher, Egypt + index: 2 +date: 23 April 2024 +bibliography: paper.bib + +--- + +# Summary + +Eaglescope is a configurable code-free interactive visualization and cohort selection tool designed for biomedical data exploration. It is designed to be hosted flexibly without the need for a dedicated server, and creates an interactive dashboard based upon a configuration file and either an API or data file. It uses visualizations of sets of features to describe and enable contextual filtering of the data. This allows for users to understand deeper patterns or anomalies within the data, and to create datasets specifically tuned to their requirements effortlessly. +Eaglescope is typically utilized either as a tool to create refined datasets tailored for training and validating machine learning AI models, or as a central hub for further exploration, allowing users to seamlessly navigate to biomedical viewers such as DICOM or whole slide imaging (WSI) platforms. +To create a dashboard, users simply need to create a file specifying the data source, configurations for each visualization, and any further desired customizations to the platform. Hosting is as straightforward as copying the static files, along with the configuration and data files if applicable, to any location capable of hosting static files. This streamlined process was intentionally designed to support the visualization of multiple datasets without added complexity or specialized requirements. Additionally, the flexibility of hosting allows for seamless scalability with demand, eliminating the need for modifications to Eaglescope itself. + +# Statement of need + +Eaglescope was initially developed to enhance the usability of interactively exploring large biomedical datasets. To achieve this, we created a versatile tool capable of supporting multiple datasets, easily reconfigurable without coding, and deployable in a serverless manner. Moreover, Eaglescope facilitates hierarchical usage, allowing dashboards to represent and link to other dashboards. Recognizing the value of visually contextualized filtering operations, we introduced a set of visualizations that display filtered data within its broader context. This approach enables users to uncover patterns in the data that might otherwise go unnoticed, fostering deeper insights and more informed decision making in biomedical research. +The Cancer Imaging Archive (TCIA) and the National Cancer Institute use Eaglescope to enable exploration and export of the large amount of data across collections and modalities and the PRISM project includes Eaglescope to facilitate dataset creation and visualization. + +# Acknowledgements + +We acknowledge all contibutors to the Eaglescope project, as well as grant support subawarded by the University of Arkansas Medical School and both financial and logistical support from the Emory Univeristy Department of Biomedical Informatics. + +# References + From 5c0dcec68d8f3f5cc4c955f7fe7e84cdfcd1eca4 Mon Sep 17 00:00:00 2001 From: Birm Date: Tue, 23 Apr 2024 14:28:21 -0400 Subject: [PATCH 2/7] add references --- paper.bib | 7 ------- paper.md | 9 +++++---- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/paper.bib b/paper.bib index 639f18e..b29aedc 100644 --- a/paper.bib +++ b/paper.bib @@ -1,10 +1,3 @@ -@online{tableau, - author = {Tableau Software}, - title = {Tableau}, - year = {2024}, - url = {https://www.tableau.com/} -} - @misc{bokeh, author = {Bokeh Contributors}, title = {Bokeh: an interactive visualization library for modern web browsers}, diff --git a/paper.md b/paper.md index 5f185eb..49cec64 100644 --- a/paper.md +++ b/paper.md @@ -1,5 +1,5 @@ --- -title: 'Eaglescope: an interactive visualization and cohort selection tool designed for biomedical data exploration.' +title: 'Eaglescope: an interactive visualization and cohort selection tool for biomedical data exploration.' tags: - javascript - interactive visualization @@ -35,10 +35,11 @@ Eaglescope is a configurable code-free interactive visualization and cohort sele Eaglescope is typically utilized either as a tool to create refined datasets tailored for training and validating machine learning AI models, or as a central hub for further exploration, allowing users to seamlessly navigate to biomedical viewers such as DICOM or whole slide imaging (WSI) platforms. To create a dashboard, users simply need to create a file specifying the data source, configurations for each visualization, and any further desired customizations to the platform. Hosting is as straightforward as copying the static files, along with the configuration and data files if applicable, to any location capable of hosting static files. This streamlined process was intentionally designed to support the visualization of multiple datasets without added complexity or specialized requirements. Additionally, the flexibility of hosting allows for seamless scalability with demand, eliminating the need for modifications to Eaglescope itself. -# Statement of need +# Statement of Need -Eaglescope was initially developed to enhance the usability of interactively exploring large biomedical datasets. To achieve this, we created a versatile tool capable of supporting multiple datasets, easily reconfigurable without coding, and deployable in a serverless manner. Moreover, Eaglescope facilitates hierarchical usage, allowing dashboards to represent and link to other dashboards. Recognizing the value of visually contextualized filtering operations, we introduced a set of visualizations that display filtered data within its broader context. This approach enables users to uncover patterns in the data that might otherwise go unnoticed, fostering deeper insights and more informed decision making in biomedical research. -The Cancer Imaging Archive (TCIA) and the National Cancer Institute use Eaglescope to enable exploration and export of the large amount of data across collections and modalities and the PRISM project includes Eaglescope to facilitate dataset creation and visualization. +Eaglescope was initially developed as a successor to abother tool [@datascope2017] to enhance the usability of interactively exploring large biomedical datasets. To achieve this, we created a versatile tool capable of supporting multiple datasets, easily reconfigurable without coding, and deployable in a serverless manner. Moreover, Eaglescope facilitates hierarchical usage, allowing dashboards to represent and link to other dashboards. Recognizing the value of visually contextualized filtering operations, we introduced a set of visualizations that display filtered data within its broader context. This approach enables users to uncover patterns in the data that might otherwise go unnoticed, fostering deeper insights and more informed decision making in biomedical research. +Eaglescope takes inspiration from Bokeh [@bokeh], cBioPortal [@cbioportal2013], and NBIA [@nbia2020] for features and user experience. +The Cancer Imaging Archive (TCIA) [@tica2013] and the National Cancer Institute use Eaglescope to enable exploration and export of the large amount of data across collections and modalities and the PRISM [@prism2020] project includes Eaglescope to facilitate dataset creation and visualization. # Acknowledgements From b06390afe3aaf19f7f3e710fcf9386e8887cd58f Mon Sep 17 00:00:00 2001 From: Birm Date: Tue, 23 Apr 2024 14:39:59 -0400 Subject: [PATCH 3/7] add figure, clean up citation --- ContextualVis.png | Bin 0 -> 89714 bytes paper.bib | 2 +- paper.md | 2 ++ 3 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 ContextualVis.png diff --git a/ContextualVis.png b/ContextualVis.png new file mode 100644 index 0000000000000000000000000000000000000000..a3b0a2c96573861e07be4f7d5ffd9974856aafd0 GIT binary patch literal 89714 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 literal 0 HcmV?d00001 diff --git a/paper.bib b/paper.bib index b29aedc..b5ea0c3 100644 --- a/paper.bib +++ b/paper.bib @@ -32,7 +32,7 @@ @article{nbia2020 } @article{tica2013, - author = {Clark, Kenneth and Vendt, Bruce and Smith, Kirk and others}, + author = {Clark, Kenneth and Vendt, Bruce and Smith, Kirk and Freymann, John and Kirby, Justin and Moore, Stephen and Phillips, Stanley and Maffit, David and Pringle, Michael and Tarbox, Lawrence and Prior, Fred}, title = {The Cancer Imaging Archive (TCIA): Maintaining and Operating a Public Information Repository}, journal = {J Digit Imaging}, year = {2013}, diff --git a/paper.md b/paper.md index 49cec64..59bb8ee 100644 --- a/paper.md +++ b/paper.md @@ -10,6 +10,7 @@ authors: - name: Nan Li equal-contrib: true affiliation: 1 + orcid: 0000-0002-3975-4809 - name: Ryan Birmingham orcid: 0000-0002-7943-6346 equal-contrib: true @@ -33,6 +34,7 @@ bibliography: paper.bib Eaglescope is a configurable code-free interactive visualization and cohort selection tool designed for biomedical data exploration. It is designed to be hosted flexibly without the need for a dedicated server, and creates an interactive dashboard based upon a configuration file and either an API or data file. It uses visualizations of sets of features to describe and enable contextual filtering of the data. This allows for users to understand deeper patterns or anomalies within the data, and to create datasets specifically tuned to their requirements effortlessly. Eaglescope is typically utilized either as a tool to create refined datasets tailored for training and validating machine learning AI models, or as a central hub for further exploration, allowing users to seamlessly navigate to biomedical viewers such as DICOM or whole slide imaging (WSI) platforms. +![Interactive Contextual Visualizations](./ContextualVis.png) To create a dashboard, users simply need to create a file specifying the data source, configurations for each visualization, and any further desired customizations to the platform. Hosting is as straightforward as copying the static files, along with the configuration and data files if applicable, to any location capable of hosting static files. This streamlined process was intentionally designed to support the visualization of multiple datasets without added complexity or specialized requirements. Additionally, the flexibility of hosting allows for seamless scalability with demand, eliminating the need for modifications to Eaglescope itself. # Statement of Need From 7a6382d0f59eb1196372a7ca464527d6d16652fe Mon Sep 17 00:00:00 2001 From: Birm Date: Tue, 23 Apr 2024 14:40:41 -0400 Subject: [PATCH 4/7] don't need auto build docker for a serverless app --- .github/workflows/buildah.yml | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 .github/workflows/buildah.yml diff --git a/.github/workflows/buildah.yml b/.github/workflows/buildah.yml deleted file mode 100644 index a5259d5..0000000 --- a/.github/workflows/buildah.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: Buildah via Dockerfile -on: [push] - -jobs: - build: - name: Build image - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - - name: Buildah Action - uses: redhat-actions/buildah-build@v2.2 - with: - image: eaglescope-edge - tags: v1 ${{ github.sha }} - dockerfiles: | - ./Dockerfile From de1e1b8d73ddb615906e6c9174f7ced8b78ff710 Mon Sep 17 00:00:00 2001 From: Birm Date: Wed, 24 Apr 2024 11:45:16 -0400 Subject: [PATCH 5/7] code auto checks --- .eslintrc.json | 9 + .github/workflows/lint_test.yml | 29 ++ Dockerfile | 2 +- package-lock.json | 17 +- package.json | 4 +- source/common/DataTranform.js | 22 -- source/common/utils.js | 16 +- source/components/Eaglescope/Eaglescope.js | 4 +- .../VisGridView/VisGridItem/VisGridItem.js | 40 +-- .../Layout/VisGridView/VisGridView.js | 2 +- source/components/Settings/Settings.js | 8 +- .../VisualTools/Chart/HorizontalBarChart.js | 4 +- .../VisualTools/Chart/ScatterChart.js | 2 +- .../MasonryComponent/MasonryComponent.js | 7 +- source/contexts/ConfigContext.js | 2 +- source/experimental/DataManager.js | 51 ---- .../Content/ContentPanel.js | 30 -- .../FieldSelectionPanel/Content/Footer.js | 14 - .../FieldSelectionPanel/Content/Item.js | 16 - .../FieldSelectionPanel.css | 277 ------------------ .../FieldSelectionPanel.js | 171 ----------- .../FieldSelectionPanel/Header/Header.js | 24 -- .../FieldSelectionPanel/Header/Tail.js | 14 - .../FieldSelectionPanel/Header/Title.js | 16 - .../FieldSelectionPanel/OperationPanel.js | 51 ---- .../FieldSelectionPanel/SearchPanel.js | 34 --- source/experimental/ImageGrid.js | 99 ------- source/experimental/ImageGridItem.js | 24 -- source/experimental/LoadData.js | 54 ---- source/experimental/RestDataSource.js | 19 -- source/experimental/filterTools.js | 50 ---- source/experimental/vegaSpecs.js | 175 ----------- source/experimental/xFilterTools.js | 44 --- source/experimental/xfRestDataSource.js | 28 -- source/hooks/useFetch.js | 2 +- source/index.js | 2 +- 36 files changed, 100 insertions(+), 1263 deletions(-) create mode 100644 .github/workflows/lint_test.yml delete mode 100644 source/common/DataTranform.js delete mode 100644 source/experimental/DataManager.js delete mode 100644 source/experimental/FieldSelectionPanel/Content/ContentPanel.js delete mode 100644 source/experimental/FieldSelectionPanel/Content/Footer.js delete mode 100644 source/experimental/FieldSelectionPanel/Content/Item.js delete mode 100644 source/experimental/FieldSelectionPanel/FieldSelectionPanel.css delete mode 100644 source/experimental/FieldSelectionPanel/FieldSelectionPanel.js delete mode 100644 source/experimental/FieldSelectionPanel/Header/Header.js delete mode 100644 source/experimental/FieldSelectionPanel/Header/Tail.js delete mode 100644 source/experimental/FieldSelectionPanel/Header/Title.js delete mode 100644 source/experimental/FieldSelectionPanel/OperationPanel.js delete mode 100644 source/experimental/FieldSelectionPanel/SearchPanel.js delete mode 100644 source/experimental/ImageGrid.js delete mode 100644 source/experimental/ImageGridItem.js delete mode 100644 source/experimental/LoadData.js delete mode 100644 source/experimental/RestDataSource.js delete mode 100644 source/experimental/filterTools.js delete mode 100644 source/experimental/vegaSpecs.js delete mode 100644 source/experimental/xFilterTools.js delete mode 100644 source/experimental/xfRestDataSource.js diff --git a/.eslintrc.json b/.eslintrc.json index 723a1d5..bf061d3 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -20,6 +20,15 @@ "no-plusplus": 0, "no-param-reassign": ["error", { "props": false }], "prefer-destructuring": ["error", { "object": true, "array": false }], + "react/no-unused-class-component-methods": 0, + "react/prop-types": 0, + "eqeqeq": 0, + "react/no-unused-state": 0, + "jsx-a11y/control-has-associated-label": 0, // don't like omitting this... + "jsx-a11y/click-events-have-key-events": 0, // don't like omitting this either... + "no-underscore-dangle":0, + "no-unused-vars":0, // for now... + "no-console": 0, "jsx-a11y/label-has-associated-control": [ "error", { diff --git a/.github/workflows/lint_test.yml b/.github/workflows/lint_test.yml new file mode 100644 index 0000000..bf659a6 --- /dev/null +++ b/.github/workflows/lint_test.yml @@ -0,0 +1,29 @@ +name: Code Quality Checks (Lint) + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + lint: + name: Run npm lint + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Use Node.js + uses: actions/setup-node@v2 + with: + node-version: '18' + + - name: Install dependencies + run: npm install --legacy-peer-deps + + - name: Run lint + run: npm run lint diff --git a/Dockerfile b/Dockerfile index cbfa252..0a0f4e2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ COPY ./ /source/ WORKDIR /source/ RUN rm -rf ./.git/ -RUN npm install +RUN npm install --legacy-peer-deps RUN npm run-script build RUN mkdir -p /var/www/html/ RUN mv /source/dist/* /var/www/html diff --git a/package-lock.json b/package-lock.json index b11ca96..04dfa0b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "BSD-3-Clause", "dependencies": { "@fortawesome/fontawesome": "^1.1.8", + "@fortawesome/fontawesome-free": "^6.5.2", "@fortawesome/fontawesome-free-solid": "^5.0.13", "@fortawesome/fontawesome-svg-core": "^6.4.2", "@fortawesome/free-regular-svg-icons": "^6.4.2", @@ -18,6 +19,7 @@ "@types/sortablejs": "^1.15.4", "array-move": "^3.0.1", "bootstrap": "^5.3.2", + "bootstrap-css-only": "^4.4.1", "crossfilter2": "^1.5.4", "d3": "^5.16.0", "jquery": "^3.7.1", @@ -1903,9 +1905,9 @@ } }, "node_modules/@fortawesome/fontawesome-free": { - "version": "5.15.4", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.15.4.tgz", - "integrity": "sha512-eYm8vijH/hpzr/6/1CJ/V/Eb1xQFW2nnUKArb3z+yUWv7HTwj6M7SP957oMjfZjAHU6qpoNc2wQvIxBLWYa/Jg==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.5.2.tgz", + "integrity": "sha512-hRILoInAx8GNT5IMkrtIt9blOdrqHOnPBH+k70aWUAqPZPgopb9G5EQJFpaBx/S8zp2fC+mPW349Bziuk1o28Q==", "hasInstallScript": true, "engines": { "node": ">=6" @@ -8367,6 +8369,15 @@ "react-dom": "*" } }, + "node_modules/mdbreact/node_modules/@fortawesome/fontawesome-free": { + "version": "5.15.4", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.15.4.tgz", + "integrity": "sha512-eYm8vijH/hpzr/6/1CJ/V/Eb1xQFW2nnUKArb3z+yUWv7HTwj6M7SP957oMjfZjAHU6qpoNc2wQvIxBLWYa/Jg==", + "hasInstallScript": true, + "engines": { + "node": ">=6" + } + }, "node_modules/mdbreact/node_modules/prop-types": { "version": "15.7.2", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", diff --git a/package.json b/package.json index e1ba3f0..944afb9 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "index.js", "scripts": { "lint": "eslint source", - "lint:fix": "eslint --fix", + "lint:fix": "eslint source --fix", "test": "npm run-script build", "build": "parcel build source/index.html --public-url ./", "dev": "parcel source/index.html" @@ -24,6 +24,7 @@ "license": "BSD-3-Clause", "dependencies": { "@fortawesome/fontawesome": "^1.1.8", + "@fortawesome/fontawesome-free": "^6.5.2", "@fortawesome/fontawesome-free-solid": "^5.0.13", "@fortawesome/fontawesome-svg-core": "^6.4.2", "@fortawesome/free-regular-svg-icons": "^6.4.2", @@ -32,6 +33,7 @@ "@types/sortablejs": "^1.15.4", "array-move": "^3.0.1", "bootstrap": "^5.3.2", + "bootstrap-css-only": "^4.4.1", "crossfilter2": "^1.5.4", "d3": "^5.16.0", "jquery": "^3.7.1", diff --git a/source/common/DataTranform.js b/source/common/DataTranform.js deleted file mode 100644 index 0c5221f..0000000 --- a/source/common/DataTranform.js +++ /dev/null @@ -1,22 +0,0 @@ -function GroupByField(data, feild) { - return d3 - .nest() - .key((d) => d[feild]) - .entries(data); -} - -function groupByFieldAndCountElements(data, field) { - return d3 - .nest() - .key((d) => d[field]) - .rollup((v) => v.length) - .entries(data); -} - -function groupByFieldAndSumElements(data, keyField, valueFeild) { - return d3 - .nest() - .key((d) => d[keyField]) - .rollup((v) => ({ length: v.length, total: d3.sum(v, (d) => parseFloat(d[valueFeild])) })) - .entries(data); -} diff --git a/source/common/utils.js b/source/common/utils.js index 1e4fdb5..fffee15 100644 --- a/source/common/utils.js +++ b/source/common/utils.js @@ -113,7 +113,7 @@ export function getLayoutConfig(chartsConfig, cols, resiziable = false) { const layout = []; const matrix = createMatrix(cols); // sort charts by priority - chartsConfig = chartsConfig.sort( + const chartsConfigSorted = chartsConfig.sort( (a, b) => b.priority - a.priority || a.title.localeCompare(b.displayName), ); @@ -124,7 +124,7 @@ export function getLayoutConfig(chartsConfig, cols, resiziable = false) { // filter out the solid chart before compute the position of the rest of charts // make an arrangement for the rest of charts - chartsConfig.forEach((chart) => { + chartsConfigSorted.forEach((chart) => { // get the size of a chart; default size is [1,1] (w,h) const size = chart.size || [1, 1]; const pos = matrix.length === 0 ? [0, 0] : getPosition(matrix, size); @@ -150,15 +150,3 @@ export function getLayoutConfig(chartsConfig, cols, resiziable = false) { return { layout, rows: matrix[0].length }; } - -// Grid includes 10px margin -export function getSizeOfGridContent(gridSize, margin) { - return [ - STUDY_VIEW_CONFIG.layout.grid.w * gridSize[0] - + (chartDimension.w - 1) * STUDY_VIEW_CONFIG.layout.gridMargin.x - - borderWidth * 2, - STUDY_VIEW_CONFIG.layout.grid.h * gridSize[1] - + (chartDimension.h - 1) * STUDY_VIEW_CONFIG.layout.gridMargin.y - - chartHeight, - ]; -} diff --git a/source/components/Eaglescope/Eaglescope.js b/source/components/Eaglescope/Eaglescope.js index e67804d..ef4fe51 100644 --- a/source/components/Eaglescope/Eaglescope.js +++ b/source/components/Eaglescope/Eaglescope.js @@ -39,12 +39,12 @@ function Eaglescope() { if (filters.length > 0) { setProgressAttrs({ now: filteredData.length, - label: `${filteredData.length}/${data.length}, ${Math.floor((filteredData.length/data.length)*100)}\%`, + label: `${filteredData.length}/${data.length}, ${Math.floor((filteredData.length / data.length) * 100)}%`, }); } else { setProgressAttrs({ now: data.length, - label: `${data.length}/${data.length}, ${Math.floor((data.length/data.length)*100)}\%`, + label: `${data.length}/${data.length}, ${Math.floor((data.length / data.length) * 100)}%`, }); } }, [filters, filteredData]); diff --git a/source/components/Layout/VisGridView/VisGridItem/VisGridItem.js b/source/components/Layout/VisGridView/VisGridItem/VisGridItem.js index fabf086..abf37be 100644 --- a/source/components/Layout/VisGridView/VisGridItem/VisGridItem.js +++ b/source/components/Layout/VisGridView/VisGridItem/VisGridItem.js @@ -1,9 +1,9 @@ import React, { useState, useContext, useEffect } from 'react'; import PropTypes from 'prop-types'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import VisGridItemContent from './VisGridItemContent/VisGridItemContent'; import VisGridItemHeader from './VisGridItemHeader/VisGridItemHeader'; import { DataContext } from '../../../../contexts/DataContext'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; // css class import './VisGridItem.css'; @@ -29,7 +29,8 @@ function VisGridItem(props) { onMouseEnter={onMouseEnterHandle} onMouseLeave={onMouseLeaveHandle} - > + - - {props.isResizing?
- -
: - } + + {props.isResizing ? ( +
+ +
+ ) : ( + + )} ); } diff --git a/source/components/Layout/VisGridView/VisGridView.js b/source/components/Layout/VisGridView/VisGridView.js index 69befaf..1be2df8 100644 --- a/source/components/Layout/VisGridView/VisGridView.js +++ b/source/components/Layout/VisGridView/VisGridView.js @@ -25,7 +25,7 @@ function VisGridView({ fullVisScreenHandler, fullScreened }) { const draggableHandle = config.GRAGGABLE ? '.draggable' : ''; const isDraggable = config.DRAGGABLE || false; const isResizable = config.RESIZABLE || false; - + const [isResizing, SetIsResizing] = useState(false); const [resizingItemId, SetResizingItemId] = useState(null); const [appLayout, setAppLayout] = useState({ diff --git a/source/components/Settings/Settings.js b/source/components/Settings/Settings.js index df4a0b7..b62fa59 100644 --- a/source/components/Settings/Settings.js +++ b/source/components/Settings/Settings.js @@ -90,8 +90,9 @@ function Settings() { return ( <> {showNewVis && } - - {config.HAS_SETTINGS&&()} + + )} scaleRef.current.y(d[fields.y]) + scaleRef.current.y.bandwidth() / 2 + 4) .text((d) => d.key) - .on('click', x=>{ + .on('click', (x) => { const filter = { id: props.id, title: props.title, @@ -84,7 +84,7 @@ function HorizontalBarChart(props) { operation: 'eq', values: x.key, }; - props.filterAdded([filter]) + props.filterAdded([filter]); }); }; diff --git a/source/components/VisualTools/Chart/ScatterChart.js b/source/components/VisualTools/Chart/ScatterChart.js index 7662a13..371ca82 100644 --- a/source/components/VisualTools/Chart/ScatterChart.js +++ b/source/components/VisualTools/Chart/ScatterChart.js @@ -99,7 +99,7 @@ export default class ScatterChart extends PureComponent { svg.selectAll('rect').remove('rect'); const startX = Math.min(this.startPosition[0], this.endPosition[0]); const startY = Math.min(this.startPosition[1], this.endPosition[1]); - const selectedArea=svg.append('rect') + const selectedArea = svg.append('rect') .attr('position', 'absolute') .attr('x', startX + this.state.margin.left) .attr('y', startY) diff --git a/source/components/VisualTools/VisGridCard/MasonryComponent/MasonryComponent.js b/source/components/VisualTools/VisGridCard/MasonryComponent/MasonryComponent.js index 30b2912..34e685c 100644 --- a/source/components/VisualTools/VisGridCard/MasonryComponent/MasonryComponent.js +++ b/source/components/VisualTools/VisGridCard/MasonryComponent/MasonryComponent.js @@ -46,7 +46,12 @@ export default class MasonryComponent extends Component { if (style.top !== undefined && Number.isInteger(style.top)) style.top += 10; if (style.left !== undefined && Number.isInteger(style.left)) style.left += 10; return ( - +
{item[fields.image] && ( { - // send event with new data - window.__data = data; - const ev = new CustomEvent('filterOut', { detail: { data, filter: {} } }); - window.dispatchEvent(ev); - console.info('filterOut event: ', ev); - }); - } else if (JSON.stringify(filter) == JSON.stringify(this.filter)) { - console.info('no change'); - } else { - this.filter = filter; - // get filtered data - this.dataSource.data(this.filter).then((data) => { - // send event with new data - window.__data = data; - const ev = new CustomEvent('filterOut', { detail: { data, filter } }); - window.dispatchEvent(ev); - console.info('filterOut event: ', ev); - }); - } - } - - initialize(e) { - this.dataSource = e.detail.dataSource; - this.dataSource.data({}).then((z) => { - // send init event with data - const ev = new CustomEvent('initData', { detail: { data: z } }); - window.__data = z; - window.__baseData = z; - window.dispatchEvent(ev); - console.info('init event: ', ev); - }); - } -} - -export default DataManager; diff --git a/source/experimental/FieldSelectionPanel/Content/ContentPanel.js b/source/experimental/FieldSelectionPanel/Content/ContentPanel.js deleted file mode 100644 index 7d1ebff..0000000 --- a/source/experimental/FieldSelectionPanel/Content/ContentPanel.js +++ /dev/null @@ -1,30 +0,0 @@ -import React, { PureComponent } from 'react'; -import Item from './Item'; -import Footer from './Footer'; - -class ContentPanel extends PureComponent { - constructor(props) { - super(props); - } - - render() { - const items = this.props.itemsExpanded ? this.props.items : this.props.items.slice(0, 4); - - let __content = items.map((item, key) => ( - - )); - - if (items.length == 0) __content = No Matching Items; - return ( -
- {__content} -
-
- ); - } -} -export default ContentPanel; diff --git a/source/experimental/FieldSelectionPanel/Content/Footer.js b/source/experimental/FieldSelectionPanel/Content/Footer.js deleted file mode 100644 index 5a5df21..0000000 --- a/source/experimental/FieldSelectionPanel/Content/Footer.js +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; - -function Footer(props) { - if (props.num <= 4) return null; - // else { - const _text = props.expanded ? 'Less...' : `${props.num - 4} More...`; - return ( -
- {_text} -
- ); - // } -} -export default Footer; diff --git a/source/experimental/FieldSelectionPanel/Content/Item.js b/source/experimental/FieldSelectionPanel/Content/Item.js deleted file mode 100644 index db1ccce..0000000 --- a/source/experimental/FieldSelectionPanel/Content/Item.js +++ /dev/null @@ -1,16 +0,0 @@ -import React, { Component } from 'react'; - -function Item(props) { -// const _id = `${props.name}`; - return ( - - ); -} - -export default Item; diff --git a/source/experimental/FieldSelectionPanel/FieldSelectionPanel.css b/source/experimental/FieldSelectionPanel/FieldSelectionPanel.css deleted file mode 100644 index f8b2e0d..0000000 --- a/source/experimental/FieldSelectionPanel/FieldSelectionPanel.css +++ /dev/null @@ -1,277 +0,0 @@ -/*facet-terms-aggregation.css*/ - - -.field-selection-panel { - z-index: 100; - position: relative; - border-bottom: 2px solid #84817a; -} - -.flex-row { - display: flex; - flex-direction: row; - box-sizing: border-box; - position: relative; - outline: none; -} - -.flex-column { - display: flex; - flex-direction: column; - box-sizing: border-box; - position: relative; - outline: none; -} - -/* Header START */ -.field-selection-panel .header { - display: flex; - flex-direction: row; - box-sizing: border-box; - position: relative; - outline: none; - -webkit-box-align: center; - -webkit-box-pack: justify; - color: #005083; - font-size: 1.7rem; - font-weight: bold; - /* cursor: pointer; */ - align-items: center; - justify-content: space-between; - padding: 1rem 0; -} - -.field-selection-panel .header .title { - cursor: pointer; -} - -.field-selection-panel .header span i { - width:1.5rem; - text-align:center; -} - -.field-selection-panel .header div.tail i, -.field-selection-panel .operation i { - text-align: center; - padding: .3rem; -} - - -.field-selection-panel .header i.fa.fa-search, -.field-selection-panel .header i.fa.fa-ellipsis-h, -.field-selection-panel .header i.fa.fa-undo, -.field-selection-panel .header i.fa.fa-times, -.field-selection-panel .search i.fa.fa-times, -.field-selection-panel .operation i.fa { - color:#6B6260; -} - -.field-selection-panel .header i.fa.fa-search:hover, -.field-selection-panel .header i.fa.fa-ellipsis-h:hover, -.field-selection-panel .header i.fa.fa-undo:hover, -.field-selection-panel .header i.fa.fa-times:hover, -.field-selection-panel .search i.fa.fa-times:hover, -.field-selection-panel .operation i.fa:hover { - cursor: pointer; - color:#337ab7; -} - -.field-selection-panel .header i.fa.fa-ellipsis-h:hover::before, -.field-selection-panel .header i.fa.fa-search:hover::before, -.field-selection-panel .header i.fa.fa-undo:hover::before, -.field-selection-panel .header i.fa.fa-times:hover::before, -.field-selection-panel .search i.fa.fa-times:hover::before, -.field-selection-panel .operation i.fa:hover::before { - text-shadow: rgba(144, 144, 144, 0.62) 0px 0px 7px; -} -/* Header END */ - -/* search panel START */ -.field-selection-panel .search, -.field-selection-panel .operation { - padding: 0 0 .5rem 1.5rem; - display: flex; - flex-direction: row; - box-sizing: border-box; - position: relative; - outline: none; -} -.field-selection-panel .operation { - font-size: 1.3rem; - line-height: 1.5; -} -.field-selection-panel .operation i.fa { - font-size: 1.5rem; -} - -.field-selection-panel .search .search-input { - width: 100%; - min-width: 0px; - height: 2.5rem; - padding: .6rem 1.2rem; - font-size: 1.4rem; - line-height: 1.42857; - color: #555555; - background-color: #FFFFFF; - border: 1px solid #CCCCCC; - box-shadow: rgba(0, 0, 0, 0.075) 0px 1px 1px inset; - transition: border-color 0.15s ease-in-out 0s, box-shadow 0.15s ease-in-out 0s; - border-radius: 4px; - margin-bottom: 6px; - outline: none; -} -.field-selection-panel .search .search-input:focus, -.field-selection-panel .search .search-input:active { - border: 2px solid #337ab7; -} - -.field-selection-panel .search .close { - font-size: 1.5rem; - position: absolute; - right: 0; - padding: .6rem; - transition: all 0.3s ease 0s; - outline: 0; - cursor: pointer; -} -/* search panel END */ - - -/* operation panel START */ -.field-selection-panel .operation span { - font-weight: bold; -} -.field-selection-panel .operation label { - cursor: pointer; -} -/* operation panel END */ - - - -.field-selection-panel .footer a { - float: right; - text-decoration: none; - font-size: 1.5rem; - color:#337ab7; - cursor: pointer; -} - -.field-selection-panel .footer a:hover, -.field-selection-panel .footer a:focus { - color: #23527c; -} - -.facet-header .operators { - display: flex; - flex-direction: row; - box-sizing: border-box; - position: relative; - outline: none; - - /* */ - color: #6B6262; - line-height: 1.48px; - font-size: 1.2em; -} - -.terms-aggregation { - display: flex; - flex-direction: column; - box-sizing: border-box; - position: relative; - outline: none; - text-align: center; -} - - - - -.terms-aggregation span.tip { - font-size: 1.3rem; - color: #ff0000; - font-weight: bold; - padding: 0.5rem; -} - - -/* .terms-list { - display: flex; - flex-direction: column; - box-sizing: border-box; - position: relative; - outline: none; -} */ - -.term { - font-size: 1.4rem; - display: flex; - flex-direction: row; - box-sizing: border-box; - position: relative; - outline: none; - /* style */ - padding: 0.3rem 0px; -} - -.term-head { - color: rgb(36, 36, 36); - text-decoration: none; - -webkit-box-align: center; - min-width: 0px; - display: flex; - align-items: center; - margin-bottom: 0.5rem; -} -.term-head label { - white-space: nowrap; - text-overflow: ellipsis; - min-width: 0; - overflow: hidden; - padding: 0 0.25rem; - margin-left: 0.3rem; - vertical-align: middle; -} -.term-tail { - margin-left: auto; - background-color: #5B5151; - font-size: 1rem; - color: white; - padding: 0.2em 0.6em 0.3em; - border-radius: 0.25em; - font-weight: bold; - height: 2rem; - cursor: pointer; -} - -.term-list-toggle { - display: flex; - flex-direction: row; - box-sizing: border-box; - position: relative; - outline: none; - - padding: 0.5rem; -} - -.term-toggle-text { - margin-left: auto; - color: #6B6245; - font-size: 1.2rem; - cursor: pointer; -} - -.content-footer a { - float: right; - text-decoration: none; - font-size: 1.5rem; - color:#337ab7; - cursor: pointer; - } - .content-footer a:hover, - .content-footer a:focus { - color: #23527c; - } - - - - diff --git a/source/experimental/FieldSelectionPanel/FieldSelectionPanel.js b/source/experimental/FieldSelectionPanel/FieldSelectionPanel.js deleted file mode 100644 index 27ba182..0000000 --- a/source/experimental/FieldSelectionPanel/FieldSelectionPanel.js +++ /dev/null @@ -1,171 +0,0 @@ -import React, { PureComponent } from 'react'; - -import PropTypes from 'prop-types'; -import Header from './Header/Header'; -import SearchPanel from './SearchPanel'; -import OperationPanel from './OperationPanel'; -import ContentPanel from './Content/ContentPanel'; - -const findMatches = (wordToMatch, list) => list.filter((item) => { - const itemName = item.name; - // here we need to figure out if word matches what was searched - const regex = new RegExp(wordToMatch, 'gi'); - return itemName.match(regex); -}); - -class FieldeSlectionPanel extends PureComponent { - constructor(props) { - super(props); - this.state = { - // for async call - hasError: null, - isLoaded: true, // - - /* UI status */ - panelExpanded: true, // Is the entire items panel expanded - itemsExpanded: false, // - isShowSearch: false, - isShowOperation: false, - /* sort status */ - - sort: 'alpha', // none, alpha, num, - isAscending: true, // asc - true, desc - false - searchText: '', - items: props.items.map((elt) => { - elt.checked = false; - return elt; - }), - /* filter status */ - // selectedChanged: this.props.selectedChanged, - // name: this.props.name || "", - // title: this.props.title || "", - // items: this.props.items || [] - }; - - // bind this - this.togglePanelHandler = this.togglePanelHandler.bind(this); - this.toggleItemsListHandler = this.toggleItemsListHandler.bind(this); - this.toggleSearch = this.toggleSearch.bind(this); - this.toggleOperation = this.toggleOperation.bind(this); - - this.setSortBy = this.setSortBy.bind(this); - this.toggleOrder = this.toggleOrder.bind(this); - - this.sort = this.sort.bind(this); - this.searchChangedHandler = this.searchChangedHandler.bind(this); - this.selectedChangeHandler = this.selectedChangeHandler.bind(this); - this.clearSelectedHandler = this.clearSelectedHandler.bind(this); - } - - setSortBy(value) { - this.setState({ sort: value }); - } - - togglePanelHandler() { - this.setState((prevState) => ({ panelExpanded: !prevState.panelExpanded })); - } - - toggleItemsListHandler() { - this.setState((prevState) => ({ itemsExpanded: !prevState.itemsExpanded })); - } - - toggleSearch() { - this.setState((prevState) => ({ isShowSearch: !prevState.isShowSearch })); - } - - toggleOperation() { - this.setState((prevState) => ({ isShowOperation: !prevState.isShowOperation })); - } - - toggleOrder() { - this.setState((prevState) => ({ isAscending: !prevState.isAscending })); - } - - sort(a, b) { - const v1 = this.state.sort === 'alpha' ? a.name : a.sum; - const v2 = this.state.sort === 'alpha' ? b.name : b.sum; - - if (v1 > v2) { - return this.state.isAscending ? 1 : -1; - } - - if (v1 < v2) { - return this.state.isAscending ? -1 : 1; - } - - return 0; - } - - searchChangedHandler(text) { - this.setState({ searchText: text }); - } - - selectedChangeHandler(e) { - const item = e.target; - this.state.items.find((i) => i.name === item.value).checked = item.checked; - this.setState((prevState) => ({ items: [...prevState.items] })); - } - - clearSelectedHandler() { - this.state.items.forEach((item) => { - item.checked = false; - }); - this.setState((prevState) => ({ items: [...prevState.items] })); - } - - render() { - // - const { hasError, isLoaded } = this.state; - - const items = this.state.searchText - ? findMatches(this.state.searchText, this.state.items.sort(this.sort)) - : this.state.items.sort(this.sort); - - // has a error - if (hasError) { - return
Something is wrong!!!
; - } - if (!isLoaded) { - return
Loading...
; - } - return ( -
-
- {this.state.isShowOperation && ( - - )} - {this.state.isShowSearch && ( - - )} - {this.state.panelExpanded && ( - - )} -
- ); - } -} - -export default FieldeSlectionPanel; - -FieldeSlectionPanel.propTypes = { - title: PropTypes.string.isRequired, - items: PropTypes.arrayOf(PropTypes.shape({})).isRequired, -}; diff --git a/source/experimental/FieldSelectionPanel/Header/Header.js b/source/experimental/FieldSelectionPanel/Header/Header.js deleted file mode 100644 index a1804d6..0000000 --- a/source/experimental/FieldSelectionPanel/Header/Header.js +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; - -import Title from './Title'; -import Tail from './Tail'; - -function Head(props) { - return ( -
- - <Tail - searchClicked={props.searchClicked} - operationClicked={props.operationClicked} - clearClicked={props.clearClicked} - items={props.items} - /> - </div> - ); -} - -export default Head; diff --git a/source/experimental/FieldSelectionPanel/Header/Tail.js b/source/experimental/FieldSelectionPanel/Header/Tail.js deleted file mode 100644 index 92a7332..0000000 --- a/source/experimental/FieldSelectionPanel/Header/Tail.js +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; - -function Tail(props) { - const result = props.items.filter((item) => item.checked == true); - return ( - <div className="tail"> - {props.operationClicked && <i className="fa fa-ellipsis-h" onClick={props.operationClicked} />} - {props.searchClicked && <i className="fa fa-search" onClick={props.searchClicked} />} - {result.length > 0 && props.clearClicked && <i className="fa fa-undo" onClick={props.clearClicked} />} - </div> - ); -} - -export default Tail; diff --git a/source/experimental/FieldSelectionPanel/Header/Title.js b/source/experimental/FieldSelectionPanel/Header/Title.js deleted file mode 100644 index 00bd4c5..0000000 --- a/source/experimental/FieldSelectionPanel/Header/Title.js +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react'; - -function Title(props) { - // _icon - const _icon = props.expanded ? 'fa fa-angle-down' : 'fa fa-angle-right'; - // style={'cursor: pointer;'} - // style={'width:1.5rem;text-align:center;'} - return ( - <span onClick={props.clicked}> - <i className={_icon} /> - {props.title} - </span> - ); -} - -export default Title; diff --git a/source/experimental/FieldSelectionPanel/OperationPanel.js b/source/experimental/FieldSelectionPanel/OperationPanel.js deleted file mode 100644 index 286a3e4..0000000 --- a/source/experimental/FieldSelectionPanel/OperationPanel.js +++ /dev/null @@ -1,51 +0,0 @@ -import React from 'react'; - -function OperationPanel(props) { - const selection = 'alpha'; - // fa-sort-up - // fa-sort-down - - // fa-sort-alpha-up - // fa-sort-alpha-down - - // fa-sort-numeric-up - // fa-sort-numeric-down - // <i className='fa fa-sort-down'></i> - const changedHandler = (e) => { - props.sortChanged(e.currentTarget.value); - }; - - const _icon = props.isAscending ? 'fa fa-sort-up' : 'fa fa-sort-down'; - return ( - <div className="operation"> - <span>Sort By:</span> -   - <label htmlFor="sortAlphaChecked"> - Alpha - <input - id="sortAlphaChecked" - name="radioGroupCollectionSort" - type="radio" - value="alpha" - defaultChecked={selection === 'alpha'} - onChange={changedHandler} - /> - </label> -   - <label htmlFor="sortNumChecked"> - Num - <input - id="sortNumChecked" - name="radioGroupCollectionSort" - type="radio" - value="num" - defaultChecked={selection === 'num'} - onChange={changedHandler} - /> - </label> -   - <i className={_icon} onClick={props.orderClicked} /> - </div> - ); -} -export default OperationPanel; diff --git a/source/experimental/FieldSelectionPanel/SearchPanel.js b/source/experimental/FieldSelectionPanel/SearchPanel.js deleted file mode 100644 index 65cc86f..0000000 --- a/source/experimental/FieldSelectionPanel/SearchPanel.js +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -function SearchPanel(props) { - const changedHandler = (e) => { - const text = e.currentTarget.value; - props.changed(text); - }; - return ( - <div className="search"> - <input - type="text" - className="search-input" - onChange={changedHandler} - onKeyUp={changedHandler} - value={props.text} - /> - {props.text && ( - <i - className="fa fa-times close" - onClick={() => props.changed('')} - role="button" - onFocus - /> - )} - </div> - ); -} -export default SearchPanel; - -SearchPanel.propTypes = { - text: PropTypes.string.isRequired, - changed: PropTypes.func.isRequired, -}; diff --git a/source/experimental/ImageGrid.js b/source/experimental/ImageGrid.js deleted file mode 100644 index d731ffb..0000000 --- a/source/experimental/ImageGrid.js +++ /dev/null @@ -1,99 +0,0 @@ -import React from 'react'; -import BaseVisualization from './BaseVisualization'; -import ImageGridItem from './ImageGridItem'; - -// should only have to worry about rendering -class ImageGrid extends BaseVisualization { - constructor(props, ctx) { - super(props, ctx); - this.onPageButton = this.onPageButton.bind(this); - this.state.currentData = {}; - this.state.page = 0; - this.state.perPage = props.perPage || 10; - this.width = this.props.w * 100 || 100; - this.height = this.props.h * 100 || 100; - this.imSize = this.props.imSize || 4; - this.style = { width: this.width, height: this.height }; - } - - onPageButton(e) { - if (e.target && e.target.innerText) { - const next_page = parseInt(e.target.innerText); - this.setState((prevState, props) => { - prevState.page = next_page; - }); - this.forceUpdate(); - } - } - - render() { - const images = []; - const thispage = this.state.filteredData.slice( - this.state.page * this.state.perPage, - (this.state.page + 1) * this.state.perPage, - ); - for (const i in thispage) { - const v = thispage[i]; - const im_url = v[this.props.urlField] || 'https://ppaas.herokuapp.com/partyparrot'; - const im_label = v[this.props.labelField] || ''; - if (im_url) { - images.push( - <ImageGridItem - url={im_url} - label={im_label} - id={`${this.id}-im-${i}`} - key={`${this.id}-im-${i}`} - w={this.imSize} - h={this.imSize} - />, - ); - } - } - - const pageBtns = []; - // min page - const _minp = Math.max(this.state.page - 5, 0); - const _maxp = Math.min( - this.state.page + 5, - this.state.filteredData.length / this.state.perPage, - ); - for (let j = _minp; j < _maxp; j++) { - if (j == this.state.page) { - pageBtns.push( - <li className="page-item" key={`${this.id}-pg-${j}`} id={`${this.id}-pg-${j}`}> - {' '} - <a className="page-link" value={j} onClick={this.onPageButton}> - <b>{j}</b> - </a> - </li>, - ); - } else { - pageBtns.push( - <li className="page-item" key={`${this.id}-pg-${j}`} id={`${this.id}-pg-${j}`}> - {' '} - <a className="page-link" value={j} onClick={this.onPageButton}> - {j} - </a> - </li>, - ); - } - } - if (this.state.ready) { - return ( - <div id={this.id} key={this.id} style={this.style}> - {images} - <ul className="pagination" id={`${this.id}-pages`}> - {pageBtns} - </ul> - </div> - ); - } - return ( - <div id={this.id} key={this.id} className="vis-loading" style={this.style}> - <p> waiting...</p> - </div> - ); - } -} - -export default ImageGrid; diff --git a/source/experimental/ImageGridItem.js b/source/experimental/ImageGridItem.js deleted file mode 100644 index 5a65c2a..0000000 --- a/source/experimental/ImageGridItem.js +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; - -// should only have to worry about rendering -class ImageGridItem extends React.Component { - constructor(props, ctx) { - super(props, ctx); - this.width = this.props.w * 10 || 10; - this.height = this.props.h * 10 || 10; - this.style = { width: this.width, height: this.height }; - // this.props.img is the url - // this.props.label is the label text - } - - render() { - return ( - <div id={this.props.id}> - <img src={this.props.url} style={this.style} /> - <span>{this.props.label}</span> - </div> - ); - } -} - -export default ImageGridItem; diff --git a/source/experimental/LoadData.js b/source/experimental/LoadData.js deleted file mode 100644 index 1b0141e..0000000 --- a/source/experimental/LoadData.js +++ /dev/null @@ -1,54 +0,0 @@ -import React from 'react'; - -class LoadData extends React.Component { - constructor(props) { - super(props); - this.state = { - error: null, - isLoaded: false, - items: [], - }; - } - - componentDidMount() { - fetch('http://144.30.2.201/locations', { - mode: 'cors', - }) - .then((res) => res.json()) - .then( - (result) => { - this.setState({ - isLoaded: true, - items: result.length, - }); - }, - // Note: it's important to handle errors here - // instead of a catch() block so that we don't swallow - // exceptions from actual bugs in components. - (error) => { - this.setState({ - isLoaded: true, - error, - }); - }, - ); - } - - render() { - const { error, isLoaded, items } = this.state; - if (error) { - return ( - <div> - Error: - {error.message} - </div> - ); - } - if (!isLoaded) { - return <div>Loading...</div>; - } - return <div>{items}</div>; - } -} - -export default LoadData; diff --git a/source/experimental/RestDataSource.js b/source/experimental/RestDataSource.js deleted file mode 100644 index b0a748d..0000000 --- a/source/experimental/RestDataSource.js +++ /dev/null @@ -1,19 +0,0 @@ -import filterTools from './filterTools.js'; - -class RestDataSource { - constructor(url) { - this._records = fetch(url).then((x) => x.json()); - - const ev = new CustomEvent('dataSourceReady', { detail: { dataSource: this } }); - window.dispatchEvent(ev); - console.info('dataSourceReady event: ', ev); - } - - data(filter) { - return new Promise((resolve, reject) => { - this._records.then((data) => { resolve(filterTools.filterData(data, filter)); }); - }); - } -} - -export default RestDataSource; diff --git a/source/experimental/filterTools.js b/source/experimental/filterTools.js deleted file mode 100644 index 8024e77..0000000 --- a/source/experimental/filterTools.js +++ /dev/null @@ -1,50 +0,0 @@ -function filterData(data, rules) { - return data.filter((y) => { - for (const rule in rules) { - var test_val; - if (rule === '__all' || rule === '__ALL') { - // we only care about VALUES, not keys - test_val = JSON.stringify(Object.values(y)); - } else { - test_val = y[rule]; - } - let broken = false; - const oprs = Object.keys(rules[rule]); - if (!broken && oprs.includes('less')) { - broken = broken || test_val >= rules[rule].less; - } - if (!broken && oprs.includes('greater')) { - broken = broken || test_val <= rules[rule].greater; - } - if (!broken && oprs.includes('match')) { - broken = broken || test_val != rules[rule].match; - } - if (!broken && oprs.includes('regex')) { - const re = new RegExp(rules[rule].regex); - broken = broken || !re.test(test_val); - } - if (broken) { - return false; - } - } - // all rules met - return true; - }); -} - -function filterMerge(filter, additions, mergeMethod) { - filter = filter || {}; - additions = additions || {}; - if (additions.hasOwnProperty('__RESET') || filter.hasOwnProperty('__RESET')) { - return { __RESET: 'true' }; - } - const outFilter = JSON.parse(JSON.stringify(filter)); - for (const rule in additions) { - outFilter[rule] = additions[rule]; - } - return outFilter; -} - -const filterTools = { filterData, filterMerge }; - -export default filterTools; diff --git a/source/experimental/vegaSpecs.js b/source/experimental/vegaSpecs.js deleted file mode 100644 index 39e4a3e..0000000 --- a/source/experimental/vegaSpecs.js +++ /dev/null @@ -1,175 +0,0 @@ -const vegaSpecs = {}; -vegaSpecs.dotPlotSpec = JSON.stringify({ - $schema: 'https://vega.github.io/schema/vega-lite/v3.json', - mark: 'tick', - selection: { - brush: { - encodings: ['x'], - type: 'interval', - }, - }, - encoding: { - x: { field: 'i0', type: 'quantitative' }, - }, -}); - -vegaSpecs.barChartSpec = JSON.stringify({ - $schema: 'https://vega.github.io/schema/vega-lite/v3.json', - mark: 'bar', - encoding: { - y: { - field: 'i0', - type: 'ordinal', - }, - x: { - aggregate: 'sum', - field: 'i1', - type: 'quantitative', - }, - }, -}); - -vegaSpecs.histSpec = JSON.stringify({ - $schema: 'https://vega.github.io/schema/vega-lite/v3.json', - mark: 'bar', - selection: { - brush: { - encodings: ['x'], - type: 'interval', - }, - }, - encoding: { - x: { - bin: true, - field: 'i0', - type: 'quantitative', - }, - y: { - aggregate: 'count', - type: 'quantitative', - }, - }, -}); - -vegaSpecs.scatterSpec = JSON.stringify({ - $schema: 'https://vega.github.io/schema/vega-lite/v3.json', - mark: 'bar', - selection: { - brush: { - encodings: ['x', 'y'], - type: 'interval', - }, - }, - mark: 'point', - encoding: { - x: { field: 'i0', type: 'quantitative' }, - y: { field: 'i1', type: 'quantitative' }, - color: { field: 'c0', type: 'nominal' }, - shape: { field: 'c1', type: 'nominal' }, - }, -}); - -vegaSpecs.parallelCoordSpec = JSON.stringify({ - $schema: 'https://vega.github.io/schema/vega-lite/v3.json', - height: 300, - transform: [ - { window: [{ op: 'count', as: 'index' }] }, - { fold: ['i1', 'i0'] }, - { - joinaggregate: [ - { op: 'min', field: 'value', as: 'min' }, - { op: 'max', field: 'value', as: 'max' }, - ], - groupby: ['key'], - }, - { - calculate: '(datum.value - datum.min) / (datum.max-datum.min)', - as: 'norm_val', - }, - { - calculate: '(datum.min + datum.max) / 2', - as: 'mi1', - }, - ], - layer: [{ - mark: { type: 'rule', color: '#ccc', tooltip: null }, - encoding: { - detail: { aggregate: 'count', type: 'quantitative' }, - x: { type: 'nominal', field: 'key' }, - }, - }, { - mark: 'line', - encoding: { - color: { type: 'nominal', field: 'f0' }, - detail: { type: 'nominal', field: 'index' }, - opacity: { value: 0.3 }, - x: { type: 'nominal', field: 'key' }, - y: { type: 'quantitative', field: 'norm_val', axis: null }, - tooltip: [{ - field: 'i1', - }, { - field: 'i0', - }], - }, - }, { - encoding: { - x: { type: 'nominal', field: 'key' }, - y: { value: 0 }, - }, - layer: [{ - mark: { type: 'text', style: 'label' }, - encoding: { - text: { aggregate: 'max', field: 'max', type: 'quantitative' }, - }, - }, { - mark: { - type: 'tick', style: 'tick', size: 8, color: '#ccc', - }, - }], - }, { - - encoding: { - x: { type: 'nominal', field: 'key' }, - y: { value: 150 }, - }, - layer: [{ - mark: { type: 'text', style: 'label' }, - encoding: { - text: { aggregate: 'min', field: 'mi1', type: 'quantitative' }, - }, - }, { - mark: { - type: 'tick', style: 'tick', size: 8, color: '#ccc', - }, - }], - }, { - encoding: { - x: { type: 'nominal', field: 'key' }, - y: { value: 300 }, - }, - layer: [{ - mark: { type: 'text', style: 'label' }, - encoding: { - text: { aggregate: 'min', field: 'min', type: 'quantitative' }, - }, - }, { - mark: { - type: 'tick', style: 'tick', size: 8, color: '#ccc', - }, - }], - }], - config: { - axisX: { - domain: false, labelAngle: 0, tickColor: '#ccc', title: null, - }, - view: { stroke: null }, - style: { - label: { - baseline: 'mi1dle', align: 'right', dx: -5, tooltip: null, - }, - tick: { orient: 'horizontal', tooltip: null }, - }, - }, -}); - -export default vegaSpecs; diff --git a/source/experimental/xFilterTools.js b/source/experimental/xFilterTools.js deleted file mode 100644 index b645645..0000000 --- a/source/experimental/xFilterTools.js +++ /dev/null @@ -1,44 +0,0 @@ -// in this mode greater and less should always be used together. -function filterData(dataObj, rules) { - if (JSON.stringify(rules) == '{}') { - return dataObj.xf.all(); - } - for (const rule in rules) { - if (dataObj.dims[rule]) { - const oprs = Object.keys(rules[rule]); - if (oprs.includes('clear')) { - dataObj.dims[rule].filter(null); - } - if (oprs.includes('match')) { - dataObj.dims[rule].filter(rules[rule].match); - } else if (oprs.includes('less') && oprs.includes('greater')) { - dataObj.dims[rule].filter([rules[rule].greater, rules[rule].less]); - } else if (oprs.includes('regex')) { - dataObj.dims[rule].filterFunction((y) => { - const re = new RegExp(rules[rule].regex); - return re.test(y); - }); - } - } else { - console.warn(`no dimension matching ${rule}`); - } - } - return dataObj.xf.allFiltered() || []; -} - -function filterMerge(filter, additions, mergeMethod) { - filter = filter || {}; - additions = additions || {}; - if (additions.hasOwnProperty('__RESET') || filter.hasOwnProperty('__RESET')) { - return { __RESET: 'true' }; - } - const outFilter = JSON.parse(JSON.stringify(filter)); - for (const rule in additions) { - outFilter[rule] = additions[rule]; - } - return outFilter; -} - -const filterTools = { filterData, filterMerge }; - -export default filterTools; diff --git a/source/experimental/xfRestDataSource.js b/source/experimental/xfRestDataSource.js deleted file mode 100644 index be9e09a..0000000 --- a/source/experimental/xfRestDataSource.js +++ /dev/null @@ -1,28 +0,0 @@ -import crossfilter from 'crossfilter2'; -import filterTools from './xFilterTools.js'; - -class RestDataSource { - constructor(url) { - this._records = fetch(url).then((x) => x.json()).then((x) => { - const xf = crossfilter(x); - const dims = {}; - for (const i in Object.keys(x[0])) { - var k = Object.keys(x[0])[i]; - dims[k] = xf.dimension((d) => d[k]); - } - dims.__ALL = xf.dimension((d) => JSON.stringify(d)); - return { xf, raw: x, dims }; - }); - const ev = new CustomEvent('dataSourceReady', { detail: { dataSource: this } }); - window.dispatchEvent(ev); - console.info('dataSourceReady event: ', ev); - } - - data(filter) { - return new Promise((resolve, reject) => { - this._records.then((data) => { resolve(filterTools.filterData(data, filter)); }); - }); - } -} - -export default RestDataSource; diff --git a/source/hooks/useFetch.js b/source/hooks/useFetch.js index 0676aa4..8b7106f 100644 --- a/source/hooks/useFetch.js +++ b/source/hooks/useFetch.js @@ -4,7 +4,7 @@ import * as d3 from 'd3'; function isNumeric(str) { if (typeof str !== 'string') return false; // we only process strings! return ( - !isNaN(str) + !Number.isNaN(str) // use type coercion to parse the _entirety_ of the string // (`parseFloat` alone does not do this)... && !Number.isNaN(parseFloat(str)) diff --git a/source/index.js b/source/index.js index c59cb26..d0cd877 100644 --- a/source/index.js +++ b/source/index.js @@ -1,6 +1,6 @@ import React from 'react'; import { createRoot } from 'react-dom/client'; -import Application from './Application.js'; +import Application from './Application'; // style import './components/VisualTools/Chart/style.css'; From dcf7aa3c35e11d043cfb379c5b3ae051e40c54d2 Mon Sep 17 00:00:00 2001 From: Birm <birm@rbirm.us> Date: Wed, 24 Apr 2024 11:47:43 -0400 Subject: [PATCH 6/7] add smoke test --- .github/workflows/smoke_test.yml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 .github/workflows/smoke_test.yml diff --git a/.github/workflows/smoke_test.yml b/.github/workflows/smoke_test.yml new file mode 100644 index 0000000..11740a4 --- /dev/null +++ b/.github/workflows/smoke_test.yml @@ -0,0 +1,29 @@ +name: Code Test (Smoke) + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + lint: + name: Run npm lint + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Use Node.js + uses: actions/setup-node@v2 + with: + node-version: '18' + + - name: Install dependencies + run: npm install --legacy-peer-deps + + - name: Run test + run: npm test From dc513f54be101fe53eef99bd7221d433e35bd55c Mon Sep 17 00:00:00 2001 From: Birm <birm@rbirm.us> Date: Wed, 24 Apr 2024 11:48:56 -0400 Subject: [PATCH 7/7] cleanup workflows a little --- .github/workflows/draft-paper.yml | 3 +++ .github/workflows/lint_test.yml | 5 ----- .github/workflows/smoke_test.yml | 8 +------- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/.github/workflows/draft-paper.yml b/.github/workflows/draft-paper.yml index 5efa396..f62d314 100644 --- a/.github/workflows/draft-paper.yml +++ b/.github/workflows/draft-paper.yml @@ -1,5 +1,8 @@ on: [push] +name: Build Paper Draft + + jobs: paper: runs-on: ubuntu-latest diff --git a/.github/workflows/lint_test.yml b/.github/workflows/lint_test.yml index bf659a6..920e30d 100644 --- a/.github/workflows/lint_test.yml +++ b/.github/workflows/lint_test.yml @@ -2,11 +2,6 @@ name: Code Quality Checks (Lint) on: push: - branches: - - main - pull_request: - branches: - - main jobs: lint: diff --git a/.github/workflows/smoke_test.yml b/.github/workflows/smoke_test.yml index 11740a4..21ab3f3 100644 --- a/.github/workflows/smoke_test.yml +++ b/.github/workflows/smoke_test.yml @@ -1,12 +1,6 @@ name: Code Test (Smoke) -on: - push: - branches: - - main - pull_request: - branches: - - main +on: [push] jobs: lint: