diff --git a/web/gui-v2/src/components/DetailViewChart.jsx b/web/gui-v2/src/components/DetailViewChart.jsx deleted file mode 100644 index 81cca27d..00000000 --- a/web/gui-v2/src/components/DetailViewChart.jsx +++ /dev/null @@ -1,47 +0,0 @@ -import React, { Suspense, lazy } from 'react'; -import { css } from '@emotion/react'; - -import SectionHeading from './SectionHeading'; -import { fallback } from '../styles/common-styles'; - -const Plot = lazy(() => import('react-plotly.js')); - -const isSSR = typeof window === "undefined"; - -const styles = { - chartContainer: css` - /* aspect-ratio: 4 / 3; */ - aspect-ratio: 16 / 9; - display: flex; - flex-direction: column; - margin: 0.5rem auto 0; - max-width: 1000px; - `, -}; - - -const Chart = ({ - config, - data, - id, - layout, - title, -}) => { - return ( - !isSSR && - Loading graph...}> - - {title} - -
- -
-
- ); -}; - -export default Chart; diff --git a/web/gui-v2/src/components/DetailViewPatents.jsx b/web/gui-v2/src/components/DetailViewPatents.jsx index c3a3c74a..a8a84409 100644 --- a/web/gui-v2/src/components/DetailViewPatents.jsx +++ b/web/gui-v2/src/components/DetailViewPatents.jsx @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import { css } from '@emotion/react'; -import { Autocomplete, Dropdown } from '@eto/eto-ui-components'; +import { Autocomplete } from '@eto/eto-ui-components'; import HeaderWithLink from './HeaderWithLink'; import StatGrid from './StatGrid'; @@ -11,7 +11,6 @@ import TrendsChart from './TrendsChart'; import overall from '../static_data/overall_data.json'; import { patentMap } from '../static_data/table_columns'; import { commas } from '../util'; -import { assemblePlotlyParams } from '../util/plotly-helpers'; const styles = { section: css` @@ -123,18 +122,6 @@ const DetailViewPatents = ({ .map(k => ({ text: patentMap[k].replace(/ patents/i, ''), val: k })) .sort((a, b) => a.text.localeCompare(b.text, 'en', { sensitivity: 'base' })); - const aiSubfieldChartData = assemblePlotlyParams( - "Trends in research....", - overall.years, - [ - [ - aiSubfieldOptions.find(e => e.val === aiSubfield)?.text, - data.patents[aiSubfield].counts - ], - ], - chartLayoutChanges, - ); - return ( <> @@ -164,8 +151,15 @@ const DetailViewPatents = ({ e.val === aiSubfield)?.text, + data.patents[aiSubfield].counts + ], + ]} id="ai-subfield-patents" + layoutChanges={chartLayoutChanges} + partialStartIndex={endIx} title={ <> Trends in {data.name}'s patenting in @@ -179,6 +173,7 @@ const DetailViewPatents = ({ /> } + years={overall.years} /> ); diff --git a/web/gui-v2/src/components/DetailViewPublications.jsx b/web/gui-v2/src/components/DetailViewPublications.jsx index 1c2ec55b..e8ca9e70 100644 --- a/web/gui-v2/src/components/DetailViewPublications.jsx +++ b/web/gui-v2/src/components/DetailViewPublications.jsx @@ -3,7 +3,6 @@ import { css } from '@emotion/react'; import { Dropdown } from '@eto/eto-ui-components'; -import Chart from './DetailViewChart'; import HeaderWithLink from './HeaderWithLink'; import StatGrid from './StatGrid'; import TableSection from './TableSection'; @@ -12,7 +11,6 @@ import TrendsChart from './TrendsChart'; import overall from '../static_data/overall_data.json'; import { articleMap } from '../static_data/table_columns'; import { commas } from '../util'; -import { assemblePlotlyParams } from '../util/plotly-helpers'; const styles = { noTopMargin: css` @@ -119,27 +117,6 @@ const DetailViewPublications = ({ { text: "Robotics", val: "robotics_pubs" }, ]; - const aiSubfieldChartData = assemblePlotlyParams( - "Trends in research....", - overall.years, - [ - [ - aiSubfieldOptions.find(e => e.val === aiSubfield)?.text, - data.articles[aiSubfield].counts - ], - ], - chartLayoutChanges, - ); - - const topConfs = assemblePlotlyParams( - <>{data.name}'s top AI conference publications, - overall.years, - [ - ["AI top conference publications", data.articles.ai_pubs_top_conf.counts], - ], - chartLayoutChanges, - ); - return ( <> @@ -161,8 +138,15 @@ const DetailViewPublications = ({ e.val === aiSubfield)?.text, + data.articles[aiSubfield].counts + ], + ]} id="ai-subfield-research" + layoutChanges={chartLayoutChanges} + partialStartIndex={endIx} title={ <> Trends in {data.name}'s research in @@ -176,11 +160,20 @@ const DetailViewPublications = ({ /> } + years={overall.years} /> -
- -
+ {data.name}'s top AI conference publications} + years={overall.years} + /> ); }; diff --git a/web/gui-v2/src/components/ListView.test.js b/web/gui-v2/src/components/ListView.test.js index 591e6689..499165cb 100644 --- a/web/gui-v2/src/components/ListView.test.js +++ b/web/gui-v2/src/components/ListView.test.js @@ -54,14 +54,14 @@ describe("ListView", () => { await user.click(removedCheckbox); expect(removedCheckbox.checked).toEqual(false); await user.click(getByRole(dialog, 'button', { name: 'Apply' })); - await waitForElementToBeRemoved(dialog); + await waitForElementToBeRemoved(() => screen.getByRole('dialog')); expect(screen.queryByRole('heading', { name: 'Add/remove columns'})).not.toBeInTheDocument(); // Verify that the changes took effect for ( const column of INITIAL_COLUMNS.filter(e => e !== REMOVED_COLUMN) ) { expect(screen.getByRole('columnheader', { name: new RegExp(column, 'i') })); } - }, 60000); + }, 90000); }); describe('groups', () => { diff --git a/web/gui-v2/src/components/TrendsChart.jsx b/web/gui-v2/src/components/TrendsChart.jsx index 201103fa..d8c8aab0 100644 --- a/web/gui-v2/src/components/TrendsChart.jsx +++ b/web/gui-v2/src/components/TrendsChart.jsx @@ -1,7 +1,13 @@ -import React from 'react'; +import React, { Suspense, lazy } from 'react'; import { css } from '@emotion/react'; -import Chart from './DetailViewChart'; +import SectionHeading from './SectionHeading'; +import { fallback } from '../styles/common-styles'; +import { assemblePlotlyParams } from '../util/plotly-helpers'; + +const Plot = lazy(() => import('react-plotly.js')); + +const isSSR = typeof window === "undefined"; const styles = { chartWrapper: css` @@ -19,26 +25,59 @@ const styles = { } } `, + chartContainer: css` + /* aspect-ratio: 4 / 3; */ + aspect-ratio: 16 / 9; + display: flex; + flex-direction: column; + margin: 0.5rem auto 0; + max-width: 1000px; + `, }; +/** + * Chart and heading showing trends over time. + * + * @param {object} props + * @param {Array<[string, Array]>} props.data + * @param {object} props.layoutChanges + * @param {boolean|undefined} props.partialStartIndex + * @param {string} props.title + * @param {Array} props.years + * @returns {JSX.Element} + */ const TrendsChart = ({ className: appliedClassName, css: appliedCss, + data: dataRaw, id: appliedId, + layoutChanges, + partialStartIndex=undefined, title, - ...otherProps + years, }) => { + const { config, data, layout } = assemblePlotlyParams(years, dataRaw, layoutChanges, { partialStartIndex }); + return (
- + {!isSSR && + Loading graph...
}> + + {title} + +
+ +
+ + } ); }; diff --git a/web/gui-v2/src/util/plotly-helpers.js b/web/gui-v2/src/util/plotly-helpers.js index b0d9d0cf..f42aaefa 100644 --- a/web/gui-v2/src/util/plotly-helpers.js +++ b/web/gui-v2/src/util/plotly-helpers.js @@ -2,34 +2,68 @@ import merge from 'lodash/merge'; import { PlotlyDefaults } from '@eto/eto-ui-components'; -const assembleChartData = (name, years, vals, otherParams) => { - return { +const assembleChartData = (name, years, vals, otherParams, options={}) => { + const { partialStartIndex } = options; + + const common = { hovertemplate: "%{y}", - mode: 'lines+markers', - type: 'scatter', - ...otherParams, + legendgroup: name, + mode: "lines+markers", name, - x: years, - y: vals, - }; + type: "scatter", + } + + // TODO / QUESTION: How do we want to handle the line/shading style for the + // year specified in `year*End`? Think about and adjust if desired. + const endSolidIx = partialStartIndex ? (partialStartIndex + 1) : undefined; + + const result = [ + { + ...common, + ...otherParams, + x: years.slice(0, endSolidIx), + y: vals.slice(0, endSolidIx), + } + ]; + + if ( partialStartIndex !== undefined ) { + result.push({ + ...common, + ...otherParams, + fill: "tozeroy", + line: { dash: "dash" }, + marker: { color: "lightgray" }, + showlegend: false, + x: years.slice(partialStartIndex), + y: vals.slice(partialStartIndex), + }); + } + + return result; }; /** * Generate the parameters for a given Plotly chart. * - * @param {string} title Overall title for the chart * @param {Array} years Array of years corresponding to `data` * @param {Array<[string, Array]>} data Array of tuples representing * individual traces in the chart, each consisting of a string for the trace * title and an array of values (which must be the same length as `years`). * @param {object} layoutChanges Any changes that should be merged into the * ETO-standard `layout` object provided by `PlotlyDefaults`. + * @param {object} options + * @param {number} options.partialStartIndex * @returns {object} An object containing the parameters for this Plotly chart: - * `{ config, data, layout, title }` + * `{ config, data, layout }` */ -export const assemblePlotlyParams = (title, years, data, layoutChanges) => { - const preparedData = data.map(([traceTitle, traceData, otherParams={}]) => { - return assembleChartData(traceTitle, years, traceData, otherParams); +export const assemblePlotlyParams = ( + years, + data, + layoutChanges={}, + options={}, +) => { + const preparedData = data.flatMap(([traceTitle, traceData, otherParams={}]) => { + return assembleChartData(traceTitle, years, traceData, otherParams, options); }); const maxY = Math.max( ...data.map(e => Math.max(...e[1])) @@ -41,7 +75,5 @@ export const assemblePlotlyParams = (title, years, data, layoutChanges) => { config, data: preparedData, layout, - title, }; }; - diff --git a/web/gui-v2/src/util/plotly-helpers.test.js b/web/gui-v2/src/util/plotly-helpers.test.js new file mode 100644 index 00000000..f892e80d --- /dev/null +++ b/web/gui-v2/src/util/plotly-helpers.test.js @@ -0,0 +1,71 @@ +import { assemblePlotlyParams } from './plotly-helpers'; + + +const TITLE = "AI top conference publications"; +const YEARS = [ 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023 ]; +const INPUT_DATA = [ + [ TITLE, [ 850, 928, 845, 843, 838, 1040, 1357, 1551, 1507, 461, 2 ] ], +]; +const LAYOUT_CHANGES = { + legend: { y: 1.15 }, + margin: { b: 50, l: 50, pad: 4, r: 50, t: 0 }, +}; +const PARTIAL_START = YEARS.findIndex(e => e === 2022); + +describe("Plotly helpers", () => { + it("create the parameters as expected", () => { + + const { config, data, layout } = assemblePlotlyParams(YEARS, INPUT_DATA, LAYOUT_CHANGES, { partialStartIndex: PARTIAL_START }); + + expect(config).toEqual({ + displayModeBar: false, + responsive: true, + }); + expect(data).toEqual([ + { + hovertemplate: "%{y}", + legendgroup: TITLE, + mode: "lines+markers", + name: TITLE, + type: "scatter", + x: [ 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 ], + y: [ 850, 928, 845, 843, 838, 1040, 1357, 1551, 1507, 461 ], + }, + { + fill: "tozeroy", + hovertemplate: "%{y}", + legendgroup: TITLE, + line: { dash: "dash" }, + marker: { color: "lightgray" }, + mode: "lines+markers", + name: TITLE, + showlegend: false, + type: "scatter", + x: [ 2022, 2023 ], + y: [ 461, 2 ], + }, + ]); + expect(layout).toEqual({ + autosize: true, + xaxis: { + fixedrange: true, + }, + yaxis: { + fixedrange: true, + }, + font: { + family: "GTZirkonRegular, Arial" + }, + legend: { + orientation: "h", + yanchor: "top", + xanchor: "center", + y: 1.15, + x: 0.5 + }, + margin: { b: 50, l: 50, pad: 4, r: 50, t: 0 }, + paper_bgcolor: "rgba(0,0,0,0)", + plot_bgcolor: "rgba(0,0,0,0)", + }); + }); +});