diff --git a/web/gui-v2/src/components/AddRemoveColumnDialog.jsx b/web/gui-v2/src/components/AddRemoveColumnDialog.jsx index 7255f97e..e5dea698 100644 --- a/web/gui-v2/src/components/AddRemoveColumnDialog.jsx +++ b/web/gui-v2/src/components/AddRemoveColumnDialog.jsx @@ -19,7 +19,7 @@ const styles = { columnDialogContents: css` font-family: GTZirkonLight; padding: 0 0.5rem 0.5rem; - width: 320px; + width: 400px; `, columnDialogTitle: css` font-family: GTZirkonRegular; diff --git a/web/gui-v2/src/components/CellStat.jsx b/web/gui-v2/src/components/CellStat.jsx index 7dd08c50..6b4b708a 100644 --- a/web/gui-v2/src/components/CellStat.jsx +++ b/web/gui-v2/src/components/CellStat.jsx @@ -6,7 +6,13 @@ import { commas } from '../util'; const styles = { cell: css` display: grid; - grid-template-columns: 60% 40%; + gap: 0.5rem; + /* + * 3.2em was chosen because it was a touch above the size needed to safely + * display a 4-digit company ranking. If we ever get above 10,000 companies + * and things start looking funky, just increase this accordingly. + */ + grid-template-columns: 1fr 3.2em; justify-content: center; & > div { @@ -15,7 +21,6 @@ const styles = { .rank { color: #a0a0a0; - margin-left: 0.5rem; } `, }; @@ -23,9 +28,11 @@ const styles = { const CellStat = ({data, colKey}) => { return (
-
{commas(data.total)}
+
+ { data?.total === null ? 'n/a' : commas(data.total) } +
- { data?.total === 0 ? '---' : <>#{data.rank} } + { (data?.total === 0 || data?.total === null) ? '---' : <>#{data.rank} }
); diff --git a/web/gui-v2/src/components/HeaderSlider.jsx b/web/gui-v2/src/components/HeaderSlider.jsx index ac159289..8583eebb 100644 --- a/web/gui-v2/src/components/HeaderSlider.jsx +++ b/web/gui-v2/src/components/HeaderSlider.jsx @@ -37,9 +37,10 @@ const HeaderSlider = ({ ); // Debounce handler for propagating internal changes to the outside + const externalHandler = (newVal) => onChange(newVal); const handleExternalChange = useMemo(() => { - return debounce(onChange, 300); - }, [onChange]); + return debounce(externalHandler, 300); + }, []); // Trigger external state change useEffect( diff --git a/web/gui-v2/src/components/ListViewTable.jsx b/web/gui-v2/src/components/ListViewTable.jsx index 38abc421..c28b3471 100644 --- a/web/gui-v2/src/components/ListViewTable.jsx +++ b/web/gui-v2/src/components/ListViewTable.jsx @@ -111,6 +111,7 @@ const styles = { const DATAKEYS_WITH_SUBKEYS = [ "articles", "patents", + "other_metrics", ]; const DEFAULT_COLUMNS = columnDefinitions @@ -125,13 +126,14 @@ const SLIDER_COLUMNS = columnDefinitions .filter(colDef => colDef.type === "slider") .map(colDef => colDef.key); +const ALL_COLUMNS = [ + ...DROPDOWN_COLUMNS, + ...SLIDER_COLUMNS, +]; + const DEFAULT_FILTER_VALUES = { - name: [], - country: [], - continent: [], - stage: [], - ai_pubs: [0, 100], - ai_patents: [0, 100], + ...DROPDOWN_COLUMNS.reduce((obj, e) => { obj[e] = []; return obj; }, {}), + ...SLIDER_COLUMNS.reduce((obj, e) => { obj[e] = [0, 100]; return obj; }, {}), }; const initialVal = (key) => { return DEFAULT_FILTER_VALUES[key]?.join(',') ?? ''; @@ -149,7 +151,6 @@ const AGGREGATE_SUM_COLUMNS = [ 'ai_patents', ]; - // Determine whether a given row matches the filters and/or selected group const filterRow = (row, filters, selectedGroupMembers) => { const filterKeys = Object.keys(filters); @@ -194,10 +195,6 @@ const filterRow = (row, filters, selectedGroupMembers) => { if ( rowVal < min || ( max < 100 && max < rowVal) ) { return false; } - } else if ( colDef.type === "stock" ) { - // TODO: Figure out how we're filtering the `market_list` column - // -- Actually - are we even wanting this column, or did I just make - // it as a placeholder? } else { console.error(`Invalid column type for key '${colDef.key}': column.type should be either "dropdown" or "slider" but is instead "${colDef.type}"`); } @@ -236,17 +233,10 @@ const ListViewTable = ({ // Store filters via the URL parameters, making the values (and setters) // accessible via an object. const filters = useMultiState( - { - name: useQueryParamString('name', initialVal('name')), - country: useQueryParamString('country', initialVal('country')), - continent: useQueryParamString('continent', initialVal('continent')), - stage: useQueryParamString('stage', initialVal('stage')), - // ... - ai_pubs: useQueryParamString('ai_pubs', initialVal('ai_pubs')), - ai_patents: useQueryParamString('ai_patents', initialVal('ai_patents')), - // ... - // market_list: useQueryParamString('market_list', ''), - }, + ALL_COLUMNS.reduce((obj, e) => { + obj[e] = useQueryParamString(e, initialVal(e)); + return obj; + }, {}), (key, val) => { if ( DROPDOWN_COLUMNS.includes(key) ) { let result = val.split(',').filter(e => e !== ""); @@ -422,11 +412,6 @@ const ListViewTable = ({ /> ); break; - case 'stock': - display_name = ( - colDef.title - ); - break; default: display_name = colDef.title; } diff --git a/web/gui-v2/src/static_data/table_columns.js b/web/gui-v2/src/static_data/table_columns.js index c18954fe..a354c609 100644 --- a/web/gui-v2/src/static_data/table_columns.js +++ b/web/gui-v2/src/static_data/table_columns.js @@ -12,6 +12,7 @@ const styles = { } `, sliderColumn: css` + min-width: 100px; width: 120px; .MuiButtonBase-root { @@ -20,6 +21,39 @@ const styles = { `, }; +/** + * Helper function to define the `extract` and `format` functions of slider + * fields in a consistent way across all columns. + * + * @param {string} dataKey + * @param {string} dataSubkey + * @returns {{ + * css: SerializedStyles, + * dataKey: string, + * dataSubkey: string, + * extract: (val: any, row: object) => any, + * format: (val: any, row: object) => ReactNode, + * initialCol: boolean, + * sortable: boolean, + * type: 'dropdown'|'slider', + * }} + */ +const generateSliderColDef = (dataKey, dataSubkey) => { + return { + css: styles.sliderColumn, + dataKey, + dataSubkey, + extract: (_val, row) => { + const res = row[dataKey][dataSubkey].total; + return res === null ? 0 : res; + }, + format: (_val, row) => , + initialCol: false, + sortable: true, + type: 'slider', + } +}; + export default [ { title: "Company", @@ -33,38 +67,155 @@ export default [ { title: "Country", key: "country", initialCol: true, type: 'dropdown' }, { title: "Region", key: "continent", initialCol: true, type: 'dropdown' }, { title: "Stage", key: "stage", initialCol: true, type: 'dropdown' }, + + { + title: "All publications", + key: "all_pubs", + ...generateSliderColDef("articles", "all_publications"), + }, + { + title: "Citation counts", + key: "citations", + ...generateSliderColDef("articles", "citation_counts"), + }, { title: "AI publications", key: "ai_pubs", - dataKey: "articles", - dataSubkey: "ai_publications", - css: styles.sliderColumn, + ...generateSliderColDef("articles", "ai_publications"), initialCol: true, - extract: (_val, row) => row.articles.ai_publications.total, - format: (_val, row) => , - sortable: true, - type: 'slider', + }, + { + title: "AI publications in top conferences", + key: "ai_pubs_top_conf", + ...generateSliderColDef("articles", "ai_pubs_top_conf"), }, { title: "AI patents", key: "ai_patents", - dataKey: "patents", - dataSubkey: "ai_patents", - css: styles.sliderColumn, + ...generateSliderColDef("patents", "ai_patents"), initialCol: true, - extract: (_val, row) => row.patents.ai_patents.total, - format: (_val, row) => , - sortable: true, - type: 'slider', }, - // { title: "AI publication intensity", key: "ai_pubs_int", initialCol: false, type: 'slider' }, - // { title: "NLP publications", key: "nlp_pubs", initialCol: true }, - // { title: "NLP patents", key: "nlp_patents", initialCol: true }, - // { title: "CV publications", key: "cv_pubs", initialCol: false }, - // { title: "CV patents", key: "cv_patents", initialCol: false }, - // { title: "Robotics publications", key: "ro_pubs", initialCol: false }, - // { title: "Robotics patents", key: "ro_patents", initialCol: false }, - // { title: "tt1 jobs (??)", key: "tt1_jobs", initialCol: false }, - // { title: "AI jobs", key: "ai_jobs", initialCol: false }, - // { title: "Stock ticker", key: "market_list", type: "stock" }, + { + title: "CV publications", + key: "cv_pubs", + ...generateSliderColDef("articles", "cv_pubs"), + }, + { + title: "NLP publications", + key: "nlp_pubs", + ...generateSliderColDef("articles", "nlp_pubs"), + }, + { + title: "Robotics publications", + key: "ro_pubs", + ...generateSliderColDef("articles", "robotics_pubs"), + }, + + { + title: "Agricultural patents", + key: "agri_patents", + ...generateSliderColDef("patents", "Agricultural"), + }, + { + title: "Banking and finance patents", + key: "finance_patents", + ...generateSliderColDef("patents", "Banking_and_Finance"), + }, + { + title: "Business patents", + key: "business_patents", + ...generateSliderColDef("patents", "Business"), + }, + { + title: "Computing in government patents", + key: "comp_in_gov_patents", + ...generateSliderColDef("patents", "Computing_in_Government"), + }, + { + title: "Document management and publishing patents", + key: "doc_mgt_patents", + ...generateSliderColDef("patents", "Document_Mgt_and_Publishing"), + }, + { + title: "Education patents", + key: "edu_patents", + ...generateSliderColDef("patents", "Education"), + }, + { + title: "Energy patents", + key: "energy_mgt_patents", + ...generateSliderColDef("patents", "Energy_Management"), + }, + { + title: "Entertainment patents", + key: "entertain_patents", + ...generateSliderColDef("patents", "Entertainment"), + }, + { + title: "Industrial and manufacturing patents", + key: "industry_patents", + ...generateSliderColDef("patents", "Industrial_and_Manufacturing"), + }, + { + title: "Life sciences patents", + key: "life_patents", + ...generateSliderColDef("patents", "Life_Sciences"), + }, + { + title: "Military patents", + key: "mil_patents", + ...generateSliderColDef("patents", "Military"), + }, + { + title: "Nanotechnology patents", + key: "nano_patents", + ...generateSliderColDef("patents", "Nanotechnology"), + }, + { + title: "Networks patents", + key: "network_patents", + ...generateSliderColDef("patents", "Networks__eg_social_IOT_etc"), + }, + { + title: "Personal devices and computing patents", + key: "personal_comp_patents", + ...generateSliderColDef("patents", "Personal_Devices_and_Computing"), + }, + { + title: "Physical sciences and engineering patents", + key: "phys_sci_patents", + ...generateSliderColDef("patents", "Physical_Sciences_and_Engineering"), + }, + { + title: "Security patents", + key: "security_patents", + ...generateSliderColDef("patents", "Security__eg_cybersecurity"), + }, + { + title: "Semiconductor patents", + key: "semiconductor_patents", + ...generateSliderColDef("patents", "Semiconductors"), + }, + { + title: "Telecommunications patents", + key: "telecom_patents", + ...generateSliderColDef("patents", "Telecommunications"), + }, + { + title: "Transportation patents", + key: "transport_patents", + ...generateSliderColDef("patents", "Transportation"), + }, + + { + title: "AI jobs", + key: "ai_jobs", + ...generateSliderColDef("other_metrics", "ai_jobs"), + initialCol: true, + }, + { + title: "Tech Tier 1 jobs", + key: "tt1_jobs", + ...generateSliderColDef("other_metrics", "tt1_jobs"), + }, ];