From 2ebd7fe835912dab018bbe0b5c1e8c6a9be1dfeb Mon Sep 17 00:00:00 2001 From: Brian Love Date: Fri, 6 Oct 2023 16:48:43 -0400 Subject: [PATCH 1/6] Update layout for company publications --- web/gui-v2/src/components/DetailViewChart.jsx | 12 +- .../src/components/DetailViewPublications.jsx | 245 ++++++++++++++---- web/gui-v2/src/components/SectionHeading.jsx | 21 ++ 3 files changed, 226 insertions(+), 52 deletions(-) create mode 100644 web/gui-v2/src/components/SectionHeading.jsx diff --git a/web/gui-v2/src/components/DetailViewChart.jsx b/web/gui-v2/src/components/DetailViewChart.jsx index f37b31c4..81cca27d 100644 --- a/web/gui-v2/src/components/DetailViewChart.jsx +++ b/web/gui-v2/src/components/DetailViewChart.jsx @@ -1,6 +1,7 @@ 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')); @@ -16,27 +17,22 @@ const styles = { margin: 0.5rem auto 0; max-width: 1000px; `, - chartTitle: css` - font-family: GTZirkonMedium; - font-size: 24px; - margin-bottom: 0; - text-align: center; - `, }; const Chart = ({ config, data, + id, layout, title, }) => { return ( !isSSR && Loading graph...}> -

+ {title} -

+
li { + align-content: center; + border: 1px solid var(--bright-blue-light); + display: grid; + gap: 0.5rem; + grid-template-columns: 80px 1fr; + max-width: 400px; + padding: 0.5rem; + + & > div { + align-items: center; + display: flex; + + &:first-of-type { + font-size: 150%; + justify-content: right; + } + } + } + `, + topResearchTopics: css` + margin: 1rem auto; + max-width: 808px; + `, + topResearchTopicsTable: css` + max-width: 808px; + `, + aiSubfieldChart: css` + h3 { + align-items: center; + display: flex; + flex-wrap: wrap; + justify-content: center; + margin: 0 auto; + width: fit-content; + + .dropdown .MuiFormControl-root { + margin: 0; + margin-left: 0.5rem; + } + } + `, }; const chartLayoutChanges = { @@ -26,21 +114,58 @@ const chartLayoutChanges = { const DetailViewPublications = ({ data, }) => { - const allVsAi = assemblePlotlyParams( - "All publications vs topics over time", + const [aiSubfield, setAiSubfield] = useState("ai_publications"); + + const averageCitations = Math.round(10 * data.articles.citation_counts.total / data.articles.all_publications.total) / 10; + + const topAiResearchTopicsColumns = [ + { display_name: "Subfield", key: "subfield" }, + { display_name: "Articles", key: "articles" }, + { display_name: "Citations per article", key: "citations" }, + { display_name: <>Growth (YEAR–YEAR), key: "growth" }, + ]; + const topAiResearchTopics = [ + { + subfield: "Computer vision", + articles: data.articles.cv_pubs.total, + citations: "???", + growth: "???", + }, + { + subfield: "Natural language processing", + articles: data.articles.nlp_pubs.total, + citations: "???", + growth: "???", + }, + { + subfield: "Robotics", + articles: data.articles.robotics_pubs.total, + citations: "???", + growth: "???", + }, + ]; + + const aiSubfieldOptions = [ + { text: "AI (all subtopics)", val: "ai_publications" }, + { text: "Computer vision", val: "cv_pubs" }, + { text: "Natural language processing", val: "nlp_pubs" }, + { text: "Robotics", val: "robotics_pubs" }, + ]; + + const aiSubfieldChartData = assemblePlotlyParams( + "Trends in research....", data.years, [ - ["All publications", data.articles.all_publications.counts], - ["AI publications", data.articles.ai_publications.counts], - ["CV publications", data.articles.cv_pubs.counts], - ["NLP publications", data.articles.nlp_pubs.counts], - ["Robotics publications", data.articles.robotics_pubs.counts], + [ + aiSubfieldOptions.find(e => e.val === aiSubfield)?.text, + data.articles[aiSubfield].counts + ], ], chartLayoutChanges, ); const topConfs = assemblePlotlyParams( - "AI top conference publications", + <>{data.name}'s top AI conference publications, data.years, [ ["AI top conference publications", data.articles.ai_pubs_top_conf.counts], @@ -48,43 +173,75 @@ const DetailViewPublications = ({ chartLayoutChanges, ); - const averageCitations = Math.round(10 * data.articles.citation_counts.total / data.articles.all_publications.total) / 10; - return ( <> -

- Radio telescope light years extraplanetary the sky calls to us billions - upon billions cosmic ocean. The only home we've ever known tesseract - tesseract dream of the mind's eye Apollonius of Perga take root and - flourish? Euclid realm of the galaxies inconspicuous motes of rock and - gas great turbulent clouds decipherment network of wormholes. -

- -

- The carbon in our apple pies circumnavigated venture worldlets Orion's - sword network of wormholes. Permanence of the stars another world - preserve and cherish that pale blue dot kindling the energy hidden in - matter muse about vastness is bearable only through love. Hearts of the - stars realm of the galaxies birth dispassionate extraterrestrial - observer vastness is bearable only through love not a sunrise but a - galaxyrise. Encyclopaedia galactica rich in heavy atoms made in the - interiors of collapsing stars descended from astronomers the only home - we've ever known. -

- -

- Brain is the seed of intelligence a mote of dust suspended in a sunbeam - light years ship of the imagination cosmic ocean muse about. Finite but - unbounded a still more glorious dawn awaits permanence of the stars - vanquish the impossible bits of moving fluff corpus callosum. Vanquish - the impossible preserve and cherish that pale blue dot citizens of - distant epochs inconspicuous motes of rock and gas. -

- - - +
+ Between {data.years[0]} and {data.years[data.years.length-1]}, {data.name} researchers released + {data.articles.ai_publications.total} AI research articles +
+ +
    +
  • +
    #{data.articles.ai_publications.rank}
    +
    in PARAT for number of AI research articles
    +
  • +
  • +
    {averageCitations}
    +
    citations per article on average (#RANK in PARAT, #RANK in the S&P 500)
    +
  • +
  • +
    NUMBER
    +
    highly-cited articles (#RANK in PARAT, #RANK in the S&P 500)
    +
  • +
  • +
    NUM%
    +
    growth in {data.name}'s public AI research (YEAR-YEAR)
    +
  • +
  • +
    {commas(data.articles.ai_pubs_top_conf.total)}
    +
    articles at top AI conferences (#{data.articles.ai_pubs_top_conf.rank} in PARAT, #RANK in the S&P 500)
    +
  • +
  • +
    NUM%
    +
    of {data.name}'s total public research was AI-focused
    +
  • +
+ +
+ + {data.name}'s top AI research topics + + + + +
+ + Trends in {data.name}'s research in + + + } + /> +
+ +
+ +
); }; diff --git a/web/gui-v2/src/components/SectionHeading.jsx b/web/gui-v2/src/components/SectionHeading.jsx new file mode 100644 index 00000000..a6340cc0 --- /dev/null +++ b/web/gui-v2/src/components/SectionHeading.jsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { css } from '@emotion/react'; + +const styles = { + chartTitle: css` + font-family: GTZirkonMedium; + font-size: 24px; + margin-bottom: 0; + text-align: center; + `, +}; + +const SectionHeading = ({ children, id }) => { + return ( +

+ {children} +

+ ); +}; + +export default SectionHeading; From 7ac646a47e6b5128b1e80022da2b73577c65f7e7 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Sun, 8 Oct 2023 22:55:39 -0400 Subject: [PATCH 2/6] Update layout for patents and workforce Update the detail page per the mockup, extracting common layout components from the Publications section to ensure consistency. --- .../src/components/DetailViewPatents.jsx | 176 +++++++++++---- .../src/components/DetailViewPublications.jsx | 213 ++++++------------ .../src/components/DetailViewWorkforce.jsx | 35 +-- web/gui-v2/src/components/StatBox.jsx | 21 +- web/gui-v2/src/components/StatGrid.jsx | 76 +++++++ web/gui-v2/src/components/StatWrapper.jsx | 2 + web/gui-v2/src/components/TableSection.jsx | 43 ++++ web/gui-v2/src/components/TextAndBigStat.jsx | 43 ++++ web/gui-v2/src/components/TrendsChart.jsx | 46 ++++ 9 files changed, 446 insertions(+), 209 deletions(-) create mode 100644 web/gui-v2/src/components/StatGrid.jsx create mode 100644 web/gui-v2/src/components/TableSection.jsx create mode 100644 web/gui-v2/src/components/TextAndBigStat.jsx create mode 100644 web/gui-v2/src/components/TrendsChart.jsx diff --git a/web/gui-v2/src/components/DetailViewPatents.jsx b/web/gui-v2/src/components/DetailViewPatents.jsx index e1281748..7744ea36 100644 --- a/web/gui-v2/src/components/DetailViewPatents.jsx +++ b/web/gui-v2/src/components/DetailViewPatents.jsx @@ -1,9 +1,26 @@ -import React from 'react'; +import React, { useState } from 'react'; +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'; +import TextAndBigStat from './TextAndBigStat'; +import TrendsChart from './TrendsChart'; +import { commas } from '../util'; import { assemblePlotlyParams } from '../util/plotly-helpers'; +const styles = { + section: css` + margin: 2rem auto 1rem; + max-width: 808px; + + h3 { + margin-bottom: 0.25rem; + } + `, +}; const chartLayoutChanges = { legend: { @@ -16,62 +33,129 @@ const chartLayoutChanges = { }, }; -const PATENT_CHART_LEGEND_GROUPINGS = { - ai_patents: 'col-1', - Security__eg_cybersecurity: 'col-1', - Education: 'col-1', - Networks__eg_social_IOT_etc: 'col-1', - Business: 'col-1', - - Military: 'col-2', - Agricultural: 'col-2', - Life_Sciences: 'col-2', - Entertainment: 'col-2', - Transportation: 'col-2', - - Semiconductors: 'col-3', - Nanotechnology: 'col-3', - Energy_Management: 'col-3', - Banking_and_Finance: 'col-3', - Telecommunications: 'col-3', - - Computing_in_Government: 'col-4', - Industrial_and_Manufacturing: 'col-4', - Physical_Sciences_and_Engineering: 'col-4', - Document_Mgt_and_Publishing: 'col-4', - Personal_Devices_and_Computing: 'col-4', -}; - const DetailViewPatents = ({ data, }) => { - const patentsData = Object.entries(PATENT_CHART_LEGEND_GROUPINGS) - .map(([key, group]) => { - const { name, counts } = data.patents[key]; - return [name.replace(/ patents/i, ''), counts, { legendgroup: group }]; - }); - - const patentsChart = assemblePlotlyParams( - "AI patents over time", + const [aiSubfield, setAiSubfield] = useState("ai_patents"); + + const statGridEntries = [ + { + stat: <>#{commas(data.patents.ai_patents.rank)}, + text: <>in PARAT for number of AI-related patents, + }, + { + stat: <>NUM%, + text: <>growth in {data.name}'s AI patenting (YEAR-YEAR), + }, + { + stat: <>NUM, + text:
AI patent applications were filed by {data.name} (YEAR_YEAR)
, + }, + { + stat: <>NUM%, + text: <>of {data.name}'s total patenting was AI-focused, + }, + ]; + + const numYears = data.years.length; + const startIx = numYears - 7; + const endIx = numYears - 2; + const patentTableColumns = [ + { display_name: "Subfield", key: "subfield" }, + { display_name: "Patents granted", key: "patents" }, + { display_name: <>Growth ({data.years[startIx]}–{data.years[endIx]}), key: "growth" }, + ]; + + const patentSubkeys = Object.keys(data.patents); + + // NOTE: for the time being, I'm hardcoding these to get data to display. The + // final implementation will require discussion and coordination. + const patentApplicationAreas = patentSubkeys.slice(0, 5).map((key) => { + const startVal = data.patents[key].counts[startIx]; + const endVal = data.patents[key].counts[endIx]; + const growth = `${Math.round((endVal - startVal) / startVal * 1000) / 10}%`; + return { + subfield: data.patents[key].name, + patents: data.patents[key].total, + growth, + }; + }); + + const patentIndustryAreas = patentSubkeys.slice(5, 10).map((key) => { + const startVal = data.patents[key].counts[startIx]; + const endVal = data.patents[key].counts[endIx]; + const growth = `${Math.round((endVal - startVal) / startVal * 1000) / 10}%`; + return { + subfield: data.patents[key].name, + patents: data.patents[key].total, + growth, + }; + }); + + const aiSubfieldOptions = [ + { text: "AI (all subtopics)", val: "ai_patents" }, + // NOTE: Disable the other subtopics for now since the keys aren't in the data. + // { text: "Computer vision", val: "cv_patents" }, + // { text: "Natural language processing", val: "nlp_patents" }, + // { text: "Robotics", val: "robotics_patents" }, + ]; + + const aiSubfieldChartData = assemblePlotlyParams( + "Trends in research....", data.years, - patentsData, + [ + [ + aiSubfieldOptions.find(e => e.val === aiSubfield)?.text, + data.patents[aiSubfield].counts + ], + ], chartLayoutChanges, ); - return ( <> -

- Radio telescope light years extraplanetary the sky calls to us billions - upon billions cosmic ocean. The only home we've ever known tesseract - tesseract dream of the mind's eye Apollonius of Perga take root and - flourish? Euclid realm of the galaxies inconspicuous motes of rock and - gas great turbulent clouds decipherment network of wormholes. -

+ Between {data.years[0]} and {data.years[data.years.length-1]}, {data.name} obtained} + bigText={<>{commas(data.patents.ai_patents.total)} AI patents} + /> + + + + Top application areas across {data.name}'s AI patents} + /> + + Top industry areas across {data.name}'s AI patents} + /> - + + Trends in {data.name}'s patenting in + + + } + /> ); }; diff --git a/web/gui-v2/src/components/DetailViewPublications.jsx b/web/gui-v2/src/components/DetailViewPublications.jsx index 3c17996a..24b5668e 100644 --- a/web/gui-v2/src/components/DetailViewPublications.jsx +++ b/web/gui-v2/src/components/DetailViewPublications.jsx @@ -1,15 +1,14 @@ import React, { useState } from 'react'; import { css } from '@emotion/react'; -import { - Dropdown, - Table, - breakpoints, -} from '@eto/eto-ui-components'; +import { Dropdown } from '@eto/eto-ui-components'; import Chart from './DetailViewChart'; import HeaderWithLink from './HeaderWithLink'; -import SectionHeading from './SectionHeading'; +import StatGrid from './StatGrid'; +import TableSection from './TableSection'; +import TextAndBigStat from './TextAndBigStat'; +import TrendsChart from './TrendsChart'; import { commas } from '../util'; import { assemblePlotlyParams } from '../util/plotly-helpers'; @@ -17,86 +16,12 @@ const styles = { noTopMargin: css` margin-top: 0; `, - sectionMargin: css` - margin: 1rem auto; + section: css` + margin: 2rem auto 1rem; max-width: 808px; - `, - sectionWithHeading: css` - margin-top: 2rem; - h3 { - margin-bottom: 0.25rem; - } - `, - aiResearch: css` - align-items: center; - display: flex; - flex-direction: column; - justify-content: center; - margin-top: 1rem; - - ${breakpoints.tablet_regular} { - flex-direction: row; - } - - big { - font-family: GTZirkonRegular; - font-size: 180%; - margin-left: 0.5rem; - } - `, - stats: css` - display: grid; - gap: 0.5rem; - grid-template-columns: minmax(0, 400px); - list-style: none; - margin: 1rem auto; - max-width: fit-content; - padding: 0; - - ${breakpoints.tablet_regular} { - grid-template-columns: repeat(2, minmax(0, 400px)); - } - & > li { - align-content: center; - border: 1px solid var(--bright-blue-light); - display: grid; - gap: 0.5rem; - grid-template-columns: 80px 1fr; - max-width: 400px; - padding: 0.5rem; - - & > div { - align-items: center; - display: flex; - - &:first-of-type { - font-size: 150%; - justify-content: right; - } - } - } - `, - topResearchTopics: css` - margin: 1rem auto; - max-width: 808px; - `, - topResearchTopicsTable: css` - max-width: 808px; - `, - aiSubfieldChart: css` h3 { - align-items: center; - display: flex; - flex-wrap: wrap; - justify-content: center; - margin: 0 auto; - width: fit-content; - - .dropdown .MuiFormControl-root { - margin: 0; - margin-left: 0.5rem; - } + margin-bottom: 0.25rem; } `, }; @@ -118,6 +43,33 @@ const DetailViewPublications = ({ const averageCitations = Math.round(10 * data.articles.citation_counts.total / data.articles.all_publications.total) / 10; + const statGridEntries = [ + { + stat: <>#{data.articles.ai_publications.rank}, + text: <>in PARAT for number of AI research articles, + }, + { + stat: <>{averageCitations}, + text: <>citations per article on average (#RANK in PARAT, #RANK in the S&P 500), + }, + { + stat: <>NUMBER, + text: <>highly-cited articles (#RANK in PARAT, #RANK in the S&P 500), + }, + { + stat: <>NUM%, + text: <>growth in {data.name}'s public AI research (YEAR-YEAR), + }, + { + stat: <>{commas(data.articles.ai_pubs_top_conf.total)}, + text: <>articles at top AI conferences (#{data.articles.ai_pubs_top_conf.rank} in PARAT, #RANK in the S&P 500), + }, + { + stat: <>NUM%, + text: <>of {data.name}'s total public research was AI-focused, + }, + ]; + const topAiResearchTopicsColumns = [ { display_name: "Subfield", key: "subfield" }, { display_name: "Articles", key: "articles" }, @@ -177,69 +129,40 @@ const DetailViewPublications = ({ <> -
- Between {data.years[0]} and {data.years[data.years.length-1]}, {data.name} researchers released - {data.articles.ai_publications.total} AI research articles -
- -
    -
  • -
    #{data.articles.ai_publications.rank}
    -
    in PARAT for number of AI research articles
    -
  • -
  • -
    {averageCitations}
    -
    citations per article on average (#RANK in PARAT, #RANK in the S&P 500)
    -
  • -
  • -
    NUMBER
    -
    highly-cited articles (#RANK in PARAT, #RANK in the S&P 500)
    -
  • -
  • -
    NUM%
    -
    growth in {data.name}'s public AI research (YEAR-YEAR)
    -
  • -
  • -
    {commas(data.articles.ai_pubs_top_conf.total)}
    -
    articles at top AI conferences (#{data.articles.ai_pubs_top_conf.rank} in PARAT, #RANK in the S&P 500)
    -
  • -
  • -
    NUM%
    -
    of {data.name}'s total public research was AI-focused
    -
  • -
- -
- - {data.name}'s top AI research topics - -
- - -
- - Trends in {data.name}'s research in - - - } - /> -
+ Between {data.years[0]} and {data.years[data.years.length-1]}, {data.name} researchers released} + bigText={<>{commas(data.articles.ai_publications.total)} AI research articles} + /> + + + + {data.name}'s top AI research topics} + /> + + + Trends in {data.name}'s research in + + + } + /> -
+
diff --git a/web/gui-v2/src/components/DetailViewWorkforce.jsx b/web/gui-v2/src/components/DetailViewWorkforce.jsx index 0ac26b69..5d4843f6 100644 --- a/web/gui-v2/src/components/DetailViewWorkforce.jsx +++ b/web/gui-v2/src/components/DetailViewWorkforce.jsx @@ -4,27 +4,34 @@ import HeaderWithLink from './HeaderWithLink'; import StatBox from './StatBox'; import StatWrapper from './StatWrapper'; +const DetailViewWorkforce = ({ + data, +}) => { + const yearSpanText = <>{data.years[0]} to {data.years[data.years.length-1]}; + + const otherMetricsWorkforceKeys = ['ai_jobs', 'tt1_jobs']; -const DetailViewWorkforce = () => { return ( <> - - + { otherMetricsWorkforceKeys.map((key) => ( + + From {yearSpanText}, {data.name} here is some explanatory text + describing how they had NUMBER jobs of the specified type + (#{data.other_metrics[key].rank} rank in PARAT + {data.in_sandp_500 && <>, #NUMBER in the S&P500}) + + } + key={key} + label={data.other_metrics[key].name} + value={data.other_metrics[key].total} + /> + ))} -

- The carbon in our apple pies circumnavigated venture worldlets Orion's - sword network of wormholes. Permanence of the stars another world - preserve and cherish that pale blue dot kindling the energy hidden in - matter muse about vastness is bearable only through love. Hearts of the - stars realm of the galaxies birth dispassionate extraterrestrial - observer vastness is bearable only through love not a sunrise but a - galaxyrise. Encyclopaedia galactica rich in heavy atoms made in the - interiors of collapsing stars descended from astronomers the only home - we've ever known. -

); }; diff --git a/web/gui-v2/src/components/StatBox.jsx b/web/gui-v2/src/components/StatBox.jsx index 62fce0a1..b9a7cd71 100644 --- a/web/gui-v2/src/components/StatBox.jsx +++ b/web/gui-v2/src/components/StatBox.jsx @@ -4,6 +4,11 @@ import { css } from '@emotion/react'; import { commas } from '../util'; const styles = { + outerWrapper: css` + column-gap: 2rem; + display: grid; + grid-template-columns: 240px 1fr; + `, statbox: css` align-items: center; background-color: var(--bright-blue-lighter); @@ -11,7 +16,7 @@ const styles = { color: var(--bright-blue); display: flex; flex-direction: column; - padding: 1rem 3rem; + padding: 1rem 1rem; `, label: css` font-size: 1.25rem; @@ -19,17 +24,25 @@ const styles = { value: css` font-size: 2.5rem; `, + description: css` + align-items: center; + display: flex; + `, }; const StatBox = ({ + description, label, value, }) => { return ( -
-
{commas(value)}
-
{label}
+
+
+
{commas(value)}
+
{label}
+
+
{description}
); }; diff --git a/web/gui-v2/src/components/StatGrid.jsx b/web/gui-v2/src/components/StatGrid.jsx new file mode 100644 index 00000000..bc39e8f4 --- /dev/null +++ b/web/gui-v2/src/components/StatGrid.jsx @@ -0,0 +1,76 @@ +import React from 'react'; +import { css } from '@emotion/react'; + +import { breakpoints } from '@eto/eto-ui-components'; + +const styles = { + stats: css` + display: grid; + gap: 0.5rem; + grid-template-columns: minmax(0, 400px); + list-style: none; + margin: 1rem auto; + max-width: fit-content; + padding: 0; + + ${breakpoints.tablet_regular} { + grid-template-columns: repeat(2, minmax(0, 400px)); + } + + & > li { + align-content: center; + border: 1px solid var(--bright-blue-light); + display: grid; + gap: 0.5rem; + grid-template-columns: 80px 1fr; + max-width: 400px; + padding: 0.5rem; + + & > div { + align-items: center; + display: flex; + + &:first-of-type { + font-size: 150%; + justify-content: right; + } + } + } + `, +}; + + +/** + * A responsive grid of boxes, each presenting a statistic and an explanation. + * + * @param {object} props + * @param {Array<{stat: ReactNode, text: ReactNode}>} props.entries + * @returns {JSX.Element} + */ +const StatGrid = ({ + className: appliedClassName, + css: appliedCss, + entries, + id: appliedId, +}) => { + return ( +
    + { + entries.map((entry) => { + return ( +
  • +
    {entry.stat}
    +
    {entry.text}
    +
  • + ); + }) + } +
+ ); +}; + +export default StatGrid; diff --git a/web/gui-v2/src/components/StatWrapper.jsx b/web/gui-v2/src/components/StatWrapper.jsx index b868212f..0e109cb1 100644 --- a/web/gui-v2/src/components/StatWrapper.jsx +++ b/web/gui-v2/src/components/StatWrapper.jsx @@ -4,6 +4,8 @@ import { css } from '@emotion/react'; const styles = { statWrapper: css` display: flex; + flex-direction: column; + gap: 2rem; justify-content: space-around; margin: 1rem auto; max-width: 720px; diff --git a/web/gui-v2/src/components/TableSection.jsx b/web/gui-v2/src/components/TableSection.jsx new file mode 100644 index 00000000..1839776f --- /dev/null +++ b/web/gui-v2/src/components/TableSection.jsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { css } from '@emotion/react'; + +import { Table } from '@eto/eto-ui-components'; + +import SectionHeading from './SectionHeading'; + +const styles = { + tableWrapper: css` + margin: 1rem auto; + max-width: 808px; + `, + table: css` + max-width: 808px; + `, +}; + +const TableSection = ({ + className: appliedClassName, + columns, + css: appliedCss, + data, + id: appliedId, + title, +}) => { + return ( +
+ + {title} + +
+ + ); +}; + +export default TableSection; diff --git a/web/gui-v2/src/components/TextAndBigStat.jsx b/web/gui-v2/src/components/TextAndBigStat.jsx new file mode 100644 index 00000000..05228331 --- /dev/null +++ b/web/gui-v2/src/components/TextAndBigStat.jsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { css } from '@emotion/react'; + +import { breakpoints } from '@eto/eto-ui-components'; + +const styles = css` + align-items: center; + display: flex; + flex-direction: column; + justify-content: center; + margin-top: 1rem; + + ${breakpoints.tablet_regular} { + flex-direction: row; + } + + big { + font-family: GTZirkonRegular; + font-size: 180%; + margin-left: 0.5rem; + } +`; + +const TextAndBigStat = ({ + bigText, + className: appliedClassName, + css: appliedCss, + id: appliedId, + smallText, +}) => { + return ( +
+ {smallText} + {bigText} +
+ ); +}; + +export default TextAndBigStat; diff --git a/web/gui-v2/src/components/TrendsChart.jsx b/web/gui-v2/src/components/TrendsChart.jsx new file mode 100644 index 00000000..201103fa --- /dev/null +++ b/web/gui-v2/src/components/TrendsChart.jsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { css } from '@emotion/react'; + +import Chart from './DetailViewChart'; + +const styles = { + chartWrapper: css` + h3 { + align-items: center; + display: flex; + flex-wrap: wrap; + justify-content: center; + margin: 0 auto; + width: fit-content; + + .dropdown .MuiFormControl-root { + margin: 0; + margin-left: 0.5rem; + } + } + `, +}; + +const TrendsChart = ({ + className: appliedClassName, + css: appliedCss, + id: appliedId, + title, + ...otherProps +}) => { + return ( +
+ +
+ ); +}; + +export default TrendsChart; From 2b10cefa77a32dd5e12a9f6ee881f7e8a766e3b2 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Wed, 11 Oct 2023 15:10:03 -0400 Subject: [PATCH 3/6] Adjust UI based on comments from Zach --- web/gui-v2/src/components/DetailViewPatents.jsx | 2 +- web/gui-v2/src/components/DetailViewPublications.jsx | 2 +- web/gui-v2/src/components/StatGrid.jsx | 6 +++--- web/gui-v2/src/components/StatWrapper.jsx | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/web/gui-v2/src/components/DetailViewPatents.jsx b/web/gui-v2/src/components/DetailViewPatents.jsx index 7744ea36..d7d255ee 100644 --- a/web/gui-v2/src/components/DetailViewPatents.jsx +++ b/web/gui-v2/src/components/DetailViewPatents.jsx @@ -17,7 +17,7 @@ const styles = { max-width: 808px; h3 { - margin-bottom: 0.25rem; + margin-bottom: 0.5rem; } `, }; diff --git a/web/gui-v2/src/components/DetailViewPublications.jsx b/web/gui-v2/src/components/DetailViewPublications.jsx index 24b5668e..db3d5c3f 100644 --- a/web/gui-v2/src/components/DetailViewPublications.jsx +++ b/web/gui-v2/src/components/DetailViewPublications.jsx @@ -21,7 +21,7 @@ const styles = { max-width: 808px; h3 { - margin-bottom: 0.25rem; + margin-bottom: 0.5rem; } `, }; diff --git a/web/gui-v2/src/components/StatGrid.jsx b/web/gui-v2/src/components/StatGrid.jsx index bc39e8f4..4089a13e 100644 --- a/web/gui-v2/src/components/StatGrid.jsx +++ b/web/gui-v2/src/components/StatGrid.jsx @@ -19,9 +19,8 @@ const styles = { & > li { align-content: center; - border: 1px solid var(--bright-blue-light); display: grid; - gap: 0.5rem; + gap: 1rem; grid-template-columns: 80px 1fr; max-width: 400px; padding: 0.5rem; @@ -31,7 +30,8 @@ const styles = { display: flex; &:first-of-type { - font-size: 150%; + font-family: GTZirkonMedium; + font-size: 200%; justify-content: right; } } diff --git a/web/gui-v2/src/components/StatWrapper.jsx b/web/gui-v2/src/components/StatWrapper.jsx index 0e109cb1..32b8e8e1 100644 --- a/web/gui-v2/src/components/StatWrapper.jsx +++ b/web/gui-v2/src/components/StatWrapper.jsx @@ -7,7 +7,7 @@ const styles = { flex-direction: column; gap: 2rem; justify-content: space-around; - margin: 1rem auto; + margin: 2rem auto; max-width: 720px; `, }; From e2b1ca7b6078c12eb2c92416765789f5b22a31c2 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Mon, 16 Oct 2023 01:14:30 -0400 Subject: [PATCH 4/6] Increase font size of text stat intro Per https://github.com/georgetown-cset/parat/pull/133#issuecomment-1762028226 --- web/gui-v2/src/components/TextAndBigStat.jsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/web/gui-v2/src/components/TextAndBigStat.jsx b/web/gui-v2/src/components/TextAndBigStat.jsx index 05228331..a9d4b32a 100644 --- a/web/gui-v2/src/components/TextAndBigStat.jsx +++ b/web/gui-v2/src/components/TextAndBigStat.jsx @@ -1,23 +1,22 @@ import React from 'react'; import { css } from '@emotion/react'; -import { breakpoints } from '@eto/eto-ui-components'; - const styles = css` align-items: center; + column-gap: 0.5rem; display: flex; - flex-direction: column; + flex-wrap: wrap; justify-content: center; margin-top: 1rem; - ${breakpoints.tablet_regular} { - flex-direction: row; + span { + font-size: 120%; + text-align: center; } big { font-family: GTZirkonRegular; font-size: 180%; - margin-left: 0.5rem; } `; @@ -34,7 +33,7 @@ const TextAndBigStat = ({ css={[styles, appliedCss]} id={appliedId} > - {smallText} + {smallText} {bigText} ); From f2a4f18bcc88504ad1fdca7b098450e434f55ddc Mon Sep 17 00:00:00 2001 From: Brian Love Date: Mon, 16 Oct 2023 03:26:28 -0400 Subject: [PATCH 5/6] Update stats and year ranges displayed on detail page --- .../src/components/DetailViewPatents.jsx | 32 ++++++---- .../src/components/DetailViewPublications.jsx | 58 +++++++++++-------- web/gui-v2/src/static_data/table_columns.js | 13 ++++- 3 files changed, 66 insertions(+), 37 deletions(-) diff --git a/web/gui-v2/src/components/DetailViewPatents.jsx b/web/gui-v2/src/components/DetailViewPatents.jsx index d7d255ee..3a522f08 100644 --- a/web/gui-v2/src/components/DetailViewPatents.jsx +++ b/web/gui-v2/src/components/DetailViewPatents.jsx @@ -8,6 +8,7 @@ import StatGrid from './StatGrid'; import TableSection from './TableSection'; import TextAndBigStat from './TextAndBigStat'; import TrendsChart from './TrendsChart'; +import { patentMap } from '../static_data/table_columns'; import { commas } from '../util'; import { assemblePlotlyParams } from '../util/plotly-helpers'; @@ -20,6 +21,13 @@ const styles = { margin-bottom: 0.5rem; } `, + trendsDropdown: css` + .MuiInputBase-input.MuiSelect-select { + align-items: center; + display: flex; + justify-content: center; + } + `, }; const chartLayoutChanges = { @@ -38,6 +46,13 @@ const DetailViewPatents = ({ }) => { const [aiSubfield, setAiSubfield] = useState("ai_patents"); + const numYears = data.years.length; + const startIx = numYears - 7; + const endIx = numYears - 2; + + const yearSpanNdash = <>{data.years[startIx]}–{data.years[endIx]}; + // const yearSpanAnd = <>{data.years[startIx]} and {data.years[endIx]}; + const statGridEntries = [ { stat: <>#{commas(data.patents.ai_patents.rank)}, @@ -45,11 +60,11 @@ const DetailViewPatents = ({ }, { stat: <>NUM%, - text: <>growth in {data.name}'s AI patenting (YEAR-YEAR), + text: <>growth in {data.name}'s AI patenting ({yearSpanNdash}), }, { stat: <>NUM, - text:
AI patent applications were filed by {data.name} (YEAR_YEAR)
, + text:
AI patent applications were filed by {data.name} ({yearSpanNdash})
, }, { stat: <>NUM%, @@ -57,9 +72,6 @@ const DetailViewPatents = ({ }, ]; - const numYears = data.years.length; - const startIx = numYears - 7; - const endIx = numYears - 2; const patentTableColumns = [ { display_name: "Subfield", key: "subfield" }, { display_name: "Patents granted", key: "patents" }, @@ -92,13 +104,8 @@ const DetailViewPatents = ({ }; }); - const aiSubfieldOptions = [ - { text: "AI (all subtopics)", val: "ai_patents" }, - // NOTE: Disable the other subtopics for now since the keys aren't in the data. - // { text: "Computer vision", val: "cv_patents" }, - // { text: "Natural language processing", val: "nlp_patents" }, - // { text: "Robotics", val: "robotics_patents" }, - ]; + // Temporarily using just a generic slice of patents + const aiSubfieldOptions = patentSubkeys.slice(0, 10).map(k => ({ text: patentMap[k], val: k })); const aiSubfieldChartData = assemblePlotlyParams( "Trends in research....", @@ -147,6 +154,7 @@ const DetailViewPatents = ({ <> Trends in {data.name}'s patenting in { const [aiSubfield, setAiSubfield] = useState("ai_publications"); + const yearSpanNdash = <>{data.years[0]}–{data.years[data.years.length-1]}; + const yearSpanAnd = <>{data.years[0]} and {data.years[data.years.length-1]}; + const averageCitations = Math.round(10 * data.articles.citation_counts.total / data.articles.all_publications.total) / 10; + const aiResearchPercent = Math.round(1000 * data.articles.ai_publications.total / data.articles.all_publications.total) / 10; + + const numYears = data.years.length; + const startIx = numYears - 7; + const endIx = numYears - 2; const statGridEntries = [ { @@ -58,14 +74,14 @@ const DetailViewPublications = ({ }, { stat: <>NUM%, - text: <>growth in {data.name}'s public AI research (YEAR-YEAR), + text: <>growth in {data.name}'s public AI research ({yearSpanNdash}), }, { stat: <>{commas(data.articles.ai_pubs_top_conf.total)}, text: <>articles at top AI conferences (#{data.articles.ai_pubs_top_conf.rank} in PARAT, #RANK in the S&P 500), }, { - stat: <>NUM%, + stat: <>{aiResearchPercent}%, text: <>of {data.name}'s total public research was AI-focused, }, ]; @@ -74,28 +90,21 @@ const DetailViewPublications = ({ { display_name: "Subfield", key: "subfield" }, { display_name: "Articles", key: "articles" }, { display_name: "Citations per article", key: "citations" }, - { display_name: <>Growth (YEAR–YEAR), key: "growth" }, - ]; - const topAiResearchTopics = [ - { - subfield: "Computer vision", - articles: data.articles.cv_pubs.total, - citations: "???", - growth: "???", - }, - { - subfield: "Natural language processing", - articles: data.articles.nlp_pubs.total, - citations: "???", - growth: "???", - }, - { - subfield: "Robotics", - articles: data.articles.robotics_pubs.total, - citations: "???", - growth: "???", - }, + { display_name: <>Growth ({data.years[startIx]}–{data.years[endIx]}), key: "growth" }, ]; + const topAiResearchTopics = Object.entries(data.articles) + .filter(([key, _val]) => ['cv_pubs', 'nlp_pubs', 'robotics_pubs'].includes(key)) + .map(([key, val]) => { + const startVal = val.counts[startIx]; + const endVal = val.counts[endIx]; + + return { + subfield: articleMap[key], + articles: val.total, + citations: "???", + growth: `${Math.round((endVal - startVal) / startVal * 1000) / 10}%`, + }; + }); const aiSubfieldOptions = [ { text: "AI (all subtopics)", val: "ai_publications" }, @@ -130,7 +139,7 @@ const DetailViewPublications = ({ Between {data.years[0]} and {data.years[data.years.length-1]}, {data.name} researchers released} + smallText={<>Between {yearSpanAnd}, {data.name} researchers released} bigText={<>{commas(data.articles.ai_publications.total)} AI research articles} /> @@ -152,6 +161,7 @@ const DetailViewPublications = ({ <> Trends in {data.name}'s research in { } }; -export default [ +const columnDefinitions = [ { title: "Company", key: "name", @@ -219,3 +219,14 @@ export default [ ...generateSliderColDef("other_metrics", "tt1_jobs"), }, ]; +export default columnDefinitions; + +export const articleMap = Object.fromEntries(columnDefinitions + .filter(e => e.dataKey === 'articles') + .map(e => ([e.dataSubkey, e.title])) +); + +export const patentMap = Object.fromEntries(columnDefinitions + .filter(e => e.dataKey === 'patents') + .map(e => ([e.dataSubkey, e.title])) +); From ec8d49a9ee99c663d112909aed889e361dd977b8 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Mon, 16 Oct 2023 03:29:48 -0400 Subject: [PATCH 6/6] Add missing keys Add missing `key` props to resolve React errors. --- web/gui-v2/src/components/DetailView.jsx | 4 ++-- web/gui-v2/src/components/DetailViewIntro.jsx | 2 +- web/gui-v2/src/components/DetailViewMoreMetadataDialog.jsx | 4 ++-- web/gui-v2/src/components/DetailViewPatents.jsx | 4 ++++ web/gui-v2/src/components/DetailViewPublications.jsx | 6 ++++++ web/gui-v2/src/components/StatGrid.jsx | 2 +- web/gui-v2/src/components/TwoColumnTable.jsx | 2 +- 7 files changed, 17 insertions(+), 7 deletions(-) diff --git a/web/gui-v2/src/components/DetailView.jsx b/web/gui-v2/src/components/DetailView.jsx index 9c414310..14b36c80 100644 --- a/web/gui-v2/src/components/DetailView.jsx +++ b/web/gui-v2/src/components/DetailView.jsx @@ -100,10 +100,10 @@ const DetailView = ({ if ( companyData ) { const breadcrumbs = [ - + ETO PARAT , - + {companyData.name} ]; diff --git a/web/gui-v2/src/components/DetailViewIntro.jsx b/web/gui-v2/src/components/DetailViewIntro.jsx index 1357ab1f..7117f9a1 100644 --- a/web/gui-v2/src/components/DetailViewIntro.jsx +++ b/web/gui-v2/src/components/DetailViewIntro.jsx @@ -69,7 +69,7 @@ const DetailViewIntro = ({ if ( data.market_filt && data.market_filt.length > 0 ) { metadata.push({ title: "Stock tickers", - value: data.market_filt.map((e) => {e.market_key}), + value: data.market_filt.map((e) => {e.market_key}), }); } diff --git a/web/gui-v2/src/components/DetailViewMoreMetadataDialog.jsx b/web/gui-v2/src/components/DetailViewMoreMetadataDialog.jsx index 6a28c628..c0e8f114 100644 --- a/web/gui-v2/src/components/DetailViewMoreMetadataDialog.jsx +++ b/web/gui-v2/src/components/DetailViewMoreMetadataDialog.jsx @@ -67,13 +67,13 @@ const MoreMetadataDialog = ({ title: 'Crunchbase', value:
{data.crunchbase.crunchbase_url} - {data.child_crunchbase.map(e => {e.crunchbase_url})} + {data.child_crunchbase.map(e => {e.crunchbase_url})}
}, { title: 'LinkedIn', value:
- {data.linkedin.map(e => {e})} + {data.linkedin.map(e => {e})}
}, { title: 'In S&P 500?', value: data.in_sandp_500 ? 'Yes' : 'No' }, diff --git a/web/gui-v2/src/components/DetailViewPatents.jsx b/web/gui-v2/src/components/DetailViewPatents.jsx index 3a522f08..7c27f5e4 100644 --- a/web/gui-v2/src/components/DetailViewPatents.jsx +++ b/web/gui-v2/src/components/DetailViewPatents.jsx @@ -55,18 +55,22 @@ const DetailViewPatents = ({ const statGridEntries = [ { + key: "ai-patents", stat: <>#{commas(data.patents.ai_patents.rank)}, text: <>in PARAT for number of AI-related patents, }, { + key: "ai-patent-growth", stat: <>NUM%, text: <>growth in {data.name}'s AI patenting ({yearSpanNdash}), }, { + key: "ai-patent-applications", stat: <>NUM, text:
AI patent applications were filed by {data.name} ({yearSpanNdash})
, }, { + key: "ai-focused-percent", stat: <>NUM%, text: <>of {data.name}'s total patenting was AI-focused, }, diff --git a/web/gui-v2/src/components/DetailViewPublications.jsx b/web/gui-v2/src/components/DetailViewPublications.jsx index 805eb067..a7056104 100644 --- a/web/gui-v2/src/components/DetailViewPublications.jsx +++ b/web/gui-v2/src/components/DetailViewPublications.jsx @@ -61,26 +61,32 @@ const DetailViewPublications = ({ const statGridEntries = [ { + key: "ai-papers", stat: <>#{data.articles.ai_publications.rank}, text: <>in PARAT for number of AI research articles, }, { + key: "average-citations", stat: <>{averageCitations}, text: <>citations per article on average (#RANK in PARAT, #RANK in the S&P 500), }, { + key: "highly-cited", stat: <>NUMBER, text: <>highly-cited articles (#RANK in PARAT, #RANK in the S&P 500), }, { + key: "ai-research-growth", stat: <>NUM%, text: <>growth in {data.name}'s public AI research ({yearSpanNdash}), }, { + key: "ai-top-conf", stat: <>{commas(data.articles.ai_pubs_top_conf.total)}, text: <>articles at top AI conferences (#{data.articles.ai_pubs_top_conf.rank} in PARAT, #RANK in the S&P 500), }, { + key: "ai-research-percent", stat: <>{aiResearchPercent}%, text: <>of {data.name}'s total public research was AI-focused, }, diff --git a/web/gui-v2/src/components/StatGrid.jsx b/web/gui-v2/src/components/StatGrid.jsx index 4089a13e..f9e67206 100644 --- a/web/gui-v2/src/components/StatGrid.jsx +++ b/web/gui-v2/src/components/StatGrid.jsx @@ -62,7 +62,7 @@ const StatGrid = ({ { entries.map((entry) => { return ( -
  • +
  • {entry.stat}
    {entry.text}
  • diff --git a/web/gui-v2/src/components/TwoColumnTable.jsx b/web/gui-v2/src/components/TwoColumnTable.jsx index 0383ea89..0513c82a 100644 --- a/web/gui-v2/src/components/TwoColumnTable.jsx +++ b/web/gui-v2/src/components/TwoColumnTable.jsx @@ -56,7 +56,7 @@ const TwoColumnTable = ({ >
    {data.map((row) => ( - +
    {row.title} {row.value ?? None found}