diff --git a/src/components/flat-table.tsx b/src/components/flat-table.tsx index 1a96165..4d3edd7 100644 --- a/src/components/flat-table.tsx +++ b/src/components/flat-table.tsx @@ -1,6 +1,7 @@ import React from "react"; import { ITableProps, IValues } from "../types"; import { DraggableTableContainer, DraggagleTableHeader } from "./draggable-table-tags"; +import { getAttrPrecisions, getAttrTypes, getAttrVisibility } from "../utils/utils"; import css from "./tables.scss"; @@ -12,8 +13,12 @@ export const FlatTable = (props: IFlatProps) => { const {selectedDataSet, collections, collectionClasses, items, mapCellsFromValues, showHeaders} = props; const collection = collections[0]; const {className} = collectionClasses[0]; + const attrVisibilities = getAttrVisibility(collections); + const collectionAttrsToUse = collection.attrs.filter(attr => !attrVisibilities[attr.title]); - const titles = collection.attrs.map(attr => attr.title); + const titles = collectionAttrsToUse.map(attr => attr.title); + const precisions = getAttrPrecisions(collections); + const attrTypes = getAttrTypes(collections); const orderedItems = items.map(item => { const orderedItem: IValues = {}; titles.forEach(title => { @@ -34,7 +39,7 @@ export const FlatTable = (props: IFlatProps) => { {collections[0].title} } - {collection.attrs.map((attr: any) => + {collectionAttrsToUse.map((attr: any) => { {orderedItems.map((item, index) => { return ( - {mapCellsFromValues(collection.id, `row-${index}`, item)} + + {mapCellsFromValues(collection.id, `row-${index}`, item, precisions, attrTypes, attrVisibilities)} + ); })} diff --git a/src/components/landscape-view.tsx b/src/components/landscape-view.tsx index d9fcab3..5b896f3 100644 --- a/src/components/landscape-view.tsx +++ b/src/components/landscape-view.tsx @@ -1,6 +1,7 @@ import React from "react"; import { ICollection, IProcessedCaseObj, ITableProps } from "../types"; import { DraggagleTableHeader } from "./draggable-table-tags"; +import { getAttrPrecisions, getAttrTypes, getAttrVisibility } from "../utils/utils"; import css from "./tables.scss"; @@ -12,6 +13,9 @@ export const LandscapeView = (props: ITableProps) => { const firstRowValues = parentColl.cases.map(caseObj => caseObj.values); const valueCount = getValueLength(firstRowValues); const className = getClassName(parentColl.cases[0]); + const precisions = getAttrPrecisions(collections); + const attrTypes = getAttrTypes(collections); + const attrVisibilities = getAttrVisibility(collections); return ( <> {showHeaders && @@ -19,10 +23,12 @@ export const LandscapeView = (props: ITableProps) => { {parentColl.name} } - {firstRowValues.map(values => mapHeadersFromValues(parentColl.id, "first-row", values))} + {firstRowValues.map(values => mapHeadersFromValues(parentColl.id, "first-row", values, attrVisibilities))} - {firstRowValues.map(values => mapCellsFromValues(parentColl.id, "first-row", values))} + {firstRowValues.map(values => + mapCellsFromValues(parentColl.id, "first-row", values, precisions, attrTypes, attrVisibilities)) + } {parentColl.cases.map((caseObj) => { @@ -46,6 +52,10 @@ export const LandscapeView = (props: ITableProps) => { const renderColFromCaseObj = (collection: ICollection, caseObj: IProcessedCaseObj, index?: number) => { const {children, values} = caseObj; const isFirstIndex = index === 0; + const precisions = getAttrPrecisions(collections); + const attrTypes = getAttrTypes(collections); + const attrVisibilities = getAttrVisibility(collections); + if (!children.length) { const className = getClassName(caseObj); return ( @@ -57,10 +67,10 @@ export const LandscapeView = (props: ITableProps) => { } {isFirstIndex && - {mapHeadersFromValues(collection.id, `first-row-${index}`, values)} + {mapHeadersFromValues(collection.id, `first-row-${index}`, values, attrVisibilities)} } - {mapCellsFromValues(collection.id, `row-${index}`, values)} + {mapCellsFromValues(collection.id, `row-${index}`, values, precisions, attrTypes, attrVisibilities)} ); } else { diff --git a/src/components/nested-table.tsx b/src/components/nested-table.tsx index f3ab55c..f9b1257 100644 --- a/src/components/nested-table.tsx +++ b/src/components/nested-table.tsx @@ -89,11 +89,12 @@ export const NestedTable = (props: IProps) => { updateInteractiveState({displayMode: e.target.value}); }, [updateInteractiveState]); - const mapHeadersFromValues = (collectionId: number, rowKey: string, values: IValues) => { + const mapHeadersFromValues = (collectionId: number, rowKey: string, values: IValues, + attrVisibilities: Record) => { return ( <> {(Object.keys(values)).map((key, index) => { - if (typeof values[key] === "string" || typeof values[key] === "number") { + if (!attrVisibilities[key] && (typeof values[key] === "string" || typeof values[key] === "number")) { return ( { ); }; - const mapCellsFromValues = (collectionId: number, rowKey: string, values: IValues, isParent?: boolean, - resizeCounter?: number, parentLevel?: number) => { + const mapCellsFromValues = (collectionId: number, rowKey: string, values: IValues, + precisions: Record, attrTypes: Record, + attrVisibilities: Record, isParent?: boolean, resizeCounter?: number, parentLevel?: number) => { return Object.keys(values).map((key, index) => { - const val = values[key]; + const isWholeNumber = values[key] % 1 === 0; + const precision = precisions[key]; + // Numbers are sometimes passed in from CODAP as a string so we use the attribute type to + // determine if it should be parsed as a number. + // Numbers that are whole numbers are treated as integers, so we should ignore the precision. + // Numeric cells that are empty should be treated as empty strings. + const val = (attrTypes[key] !== "numeric" && attrTypes[key] !== null) + || (values[key] === "") + || (typeof values[key] !== "number") + ? values[key] + : isWholeNumber + ? parseInt(values[key],10) + : precision !== undefined + ? (parseFloat(values[key])).toFixed(precision) + : (parseFloat(values[key])).toFixed(2); // default to 2 decimal places + if (attrVisibilities[key]) { + return null; + } if (typeof val === "string" || typeof val === "number") { return ( , + attrTypes: Record, + attrVisibilities: Record, isParent: boolean, resizeCounter: number, parentLevel?: number} & ITableProps; export const PortraitViewRow = (props: PortraitViewRowProps) => { - const {paddingStyle, mapCellsFromValues, mapHeadersFromValues, showHeaders, + const {paddingStyle, mapCellsFromValues, mapHeadersFromValues, showHeaders, precisions, attrTypes, attrVisibilities, getClassName, collectionId, caseObj, index, isParent, resizeCounter, parentLevel} = props; const {children, values} = caseObj; if (!children.length) { return ( - {mapCellsFromValues(collectionId, `row-${index}`, values)} + {mapCellsFromValues(collectionId, `row-${index}`, values, precisions, attrTypes, attrVisibilities)} ); } else { return ( <> {index === 0 && - {mapHeadersFromValues(collectionId, `first-row-${index}`, values)} + {mapHeadersFromValues(collectionId, `first-row-${index}`, values, attrVisibilities)} {showHeaders ? ( {children[0].collection.name} ) : } } - {mapCellsFromValues(collectionId, `parent-row-${index}`, values, isParent, resizeCounter, parentLevel)} + {mapCellsFromValues(collectionId, `parent-row-${index}`, values, precisions, attrTypes, attrVisibilities, + isParent, resizeCounter, parentLevel)} @@ -49,7 +54,8 @@ export const PortraitViewRow = (props: PortraitViewRowProps) => { return ( - {mapHeadersFromValues(child.collection.id, `child-row-${index}-${i}`, child.values)} + {mapHeadersFromValues(child.collection.id, `child-row-${index}-${i}`, child.values, + attrVisibilities)} @@ -104,6 +110,9 @@ export const PortraitView = (props: ITableProps) => { const {className} = collectionClasses[0]; const firstRowValues = parentColl.cases.map(caseObj => caseObj.values); const valueCount = getValueLength(firstRowValues); + const precisions = getAttrPrecisions(collections); + const attrTypes = getAttrTypes(collections); + const attrVisibilities = getAttrVisibility(collections); return ( @@ -122,6 +131,9 @@ export const PortraitView = (props: ITableProps) => { collectionId={caseObj.collection.id} caseObj={caseObj} index={index} + precisions={precisions} + attrTypes={attrTypes} + attrVisibilities={attrVisibilities} isParent={true} resizeCounter={resizeCounter} parentLevel={0} diff --git a/src/hooks/useCodapState.tsx b/src/hooks/useCodapState.tsx index 4d4ae63..191564b 100644 --- a/src/hooks/useCodapState.tsx +++ b/src/hooks/useCodapState.tsx @@ -100,6 +100,7 @@ export const useCodapState = () => { case `updateAttributes`: case `hideAttributes`: case `showAttributes`: + case `unhideAttributes`: refreshDataSetInfo(); break; case `updateDataContext`: // includes renaming dataset, so we have to redo the menu diff --git a/src/types.tsx b/src/types.tsx index 9ba0c2c..2008040 100644 --- a/src/types.tsx +++ b/src/types.tsx @@ -55,9 +55,11 @@ export interface ITableProps { getClassName: (caseObj: IProcessedCaseObj) => string, selectedDataSet: IDataSet, collections: Array, - mapCellsFromValues: (collectionId: number, rowKey: string, values: IValues, isParent?: boolean, - resizeCounter?: number, parentLevel?: number) => void, - mapHeadersFromValues: (collectionId: number, rowKey: string, values: IValues) => void, + mapCellsFromValues: (collectionId: number, rowKey: string, values: IValues, precisions: Record, + attrTypes: Record, attrVisibilities: Record, + isParent?: boolean, resizeCounter?: number, parentLevel?: number) => void, + mapHeadersFromValues: (collectionId: number, rowKey: string, values: IValues, + attrVisibilities: Record) => void, getValueLength: (firstRow: Array) => number paddingStyle: Record } diff --git a/src/utils/utils.ts b/src/utils/utils.ts new file mode 100644 index 0000000..a9ca49f --- /dev/null +++ b/src/utils/utils.ts @@ -0,0 +1,39 @@ +import { ICollections } from "../types"; + +const getAllAttributesFromCollections = (collections: ICollections[]) => { + const attrArray: any[] = []; + collections.forEach((collection: any) => { + attrArray.push(...collection.attrs); + }); + return attrArray; +}; + +export const getAttrPrecisions = (collections: any) => { + const attrs = getAllAttributesFromCollections(collections); + const precisions = attrs.reduce((acc: Record, attr: any) => { + const numPrecision = parseInt(attr.precision, 10); + acc[attr.name] = isNaN(numPrecision) ? 2 : numPrecision; + return acc; + }, {}); + return precisions; +}; + +export const getAttrTypes = (collections: any) => { + const attrs = getAllAttributesFromCollections(collections); + const attrTypes = attrs.reduce( + (acc: Record, attr: any) => { + acc[attr.name] = attr.type || null; + return acc; + }, {}); + return attrTypes; +}; + +export const getAttrVisibility = (collections: any) => { + const attrs = getAllAttributesFromCollections(collections); + const attrVisibilities = attrs.reduce( + (acc: Record, attr: any) => { + acc[attr.name] = attr.hidden || false; + return acc; + }, {}); + return attrVisibilities; +};