diff --git a/libs/shared/lib/components/charts/Axis/axis.stories.tsx b/libs/shared/lib/components/charts/Axis/axis.stories.tsx new file mode 100644 index 0000000000000000000000000000000000000000..40857f3d6d7c36ce71ea5897785bf6efd47bf836 --- /dev/null +++ b/libs/shared/lib/components/charts/Axis/axis.stories.tsx @@ -0,0 +1,31 @@ +// BarPlot.stories.tsx +import React from 'react'; +import { Meta, Story } from '@storybook/react'; +import AxisComponent, { AxisComponentProps } from '.'; +import * as d3 from 'd3'; + +const Component: Meta<AxisComponentProps> = { + title: 'Visual charts/Charts/Axis', + component: AxisComponent, + decorators: [ + (Story) => ( + <div className="w-full h-full flex flex-row justify-center"> + <svg className="border border-gray-300" width="300" height="200"> + <Story /> + </svg> + </div> + ), + ], +}; + +export default Component; + +const Template: Story<AxisComponentProps> = (args) => <AxisComponent {...args} />; + +export const CategoricalData = Template.bind({}); +CategoricalData.args = { + scale: d3.scaleLinear().domain([0, 10]).range([200, 0]), + orientation: 'bottom', + ticks: 2, + type: 'categorical', +}; diff --git a/libs/shared/lib/components/charts/Axis/index.tsx b/libs/shared/lib/components/charts/Axis/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..906e3f78c2558441aebff4afd60890a7d37a92be --- /dev/null +++ b/libs/shared/lib/components/charts/Axis/index.tsx @@ -0,0 +1,57 @@ +import React, { useEffect, useRef } from 'react'; +import * as d3 from 'd3'; + +export type AxisComponentProps = { + scale: d3.AxisScale<number | { valueOf(): number }>; + orientation: 'left' | 'bottom'; + ticks?: number; + type: string; + range: number[]; +}; + +const AxisComponent: React.FC<AxisComponentProps> = ({ scale, orientation, ticks, type, range }) => { + const yAxis1Ref = useRef<SVGGElement>(null); + //const yAxis2Ref = useRef<SVGGElement>(null); + const groupRef = useRef<SVGGElement>(null); + + useEffect(() => { + if (!yAxis1Ref.current || !groupRef.current) return; + + //const axisElement = d3.select(axisRef.current); + const yAxis1Element = d3.select(yAxis1Ref.current); + //const yAxis2Element = d3.select(yAxis2Ref.current); + + const groupElement = d3.select(groupRef.current); + + //const axisGenerator = orientation === 'left' ? d3.axisLeft(scale) : orientation === 'bottom' ? d3.axisBottom(scale) : null; + + // Append the axes to their respective containers + if (type === 'categorical') { + const yAxis1 = d3.axisBottom(scale); //.tickFormat(d3.format('d')); // to show 0 without decimals + + yAxis1Element.call(yAxis1); + } else { + const xAxis = d3 + .axisBottom(scale) + .tickValues([Math.round((range[0] + range[1]) / 2.0), range[1]]) + .tickFormat(d3.format('.2s')); + + yAxis1Element.call(xAxis); + } + + // Style axis elements + groupElement.selectAll('.domain').style('stroke', 'hsl(var(--clr-sec--400))'); + groupElement.selectAll('.tick line').style('stroke', 'hsl(var(--clr-sec--400))'); + groupElement.selectAll('.tick text').attr('class', 'font-mono').style('stroke', 'none').style('fill', 'hsl(var(--clr-sec--500))'); + + // Call axis generator if available + }, []); + + return ( + <g ref={groupRef}> + <g ref={yAxis1Ref} /> + </g> + ); +}; + +export default AxisComponent; diff --git a/libs/shared/lib/components/charts/barplot/index.tsx b/libs/shared/lib/components/charts/barplot/index.tsx index 034b5f1f6730044b1c26f4bff9ff6e21916249f6..5b44e020939db52b581b19f1684201c54ed8f08d 100644 --- a/libs/shared/lib/components/charts/barplot/index.tsx +++ b/libs/shared/lib/components/charts/barplot/index.tsx @@ -20,34 +20,37 @@ export const BarPlot = ({ typeBarPlot: typeBarplot, numBins, data }: BarPlotProp const heightSVG: number = +svgRef.current.clientHeight; setDimensions({ width: widthSVG, height: heightSVG }); - - const marginPercentage = { - top: 0.245, - right: 0.245, - bottom: 0.245, - left: 0.245, - }; - const margin = { - top: marginPercentage.top * heightSVG, - right: marginPercentage.right * heightSVG, - bottom: marginPercentage.bottom * heightSVG, - left: marginPercentage.left * heightSVG, - }; - const svgPlot = d3.select(svgRef.current); svgPlot.selectAll('*').remove(); - const groupMargin = svgPlot.append('g').attr('transform', `translate(${margin.left},${margin.top})`); - const widthSVGwithinMargin: number = widthSVG - margin.left - margin.right; - const heightSVGwithinMargin: number = heightSVG - margin.top - margin.bottom; + + let groupMargin: d3.Selection<SVGGElement, unknown, null, undefined>; + let widthSVGwithinMargin: number = 0.0; + let heightSVGwithinMargin: number = 0.0; // Barplot for categorical data if (typeBarplot == 'categorical') { + let marginPercentage = { + top: 0.19, + right: 0.02, + bottom: 0.19, + left: 0.19, + }; + let margin = { + top: marginPercentage.top * heightSVG, + right: marginPercentage.right * widthSVG, + bottom: marginPercentage.bottom * heightSVG, + left: marginPercentage.left * widthSVG, + }; + + groupMargin = svgPlot.append('g').attr('transform', `translate(${margin.left},${margin.top})`); + widthSVGwithinMargin = widthSVG - margin.left - margin.right; + heightSVGwithinMargin = heightSVG - margin.top - margin.bottom; + // Data processing const dataFiltered = data.filter((item) => item.category !== undefined); const dataSorted = dataFiltered.sort((a, b) => b.count - a.count); const maxCount = d3.max(dataSorted, (d) => d.count) ?? 1; - const xScale = d3 .scaleBand() .range([0, widthSVGwithinMargin]) @@ -58,8 +61,10 @@ export const BarPlot = ({ typeBarPlot: typeBarplot, numBins, data }: BarPlotProp ) .padding(0.2); - const yScale = d3.scaleLinear().domain([0, maxCount]).range([heightSVGwithinMargin, 0]); //.nice(); + // send this + const yScale = d3.scaleLinear().domain([0, maxCount]).range([heightSVGwithinMargin, 0]); + // here out to axis component const yAxis1 = d3.axisLeft(yScale).tickValues([0]).tickFormat(d3.format('d')); // to show 0 without decimanls let yAxis2: d3.Axis<d3.NumberValue>; @@ -78,7 +83,8 @@ export const BarPlot = ({ typeBarPlot: typeBarplot, numBins, data }: BarPlotProp .attr('y', (d) => yScale(d.count)) .attr('width', xScale.bandwidth()) .attr('height', (d) => heightSVGwithinMargin - yScale(d.count)) - .attr('class', 'fill-primary stroke-white') + .attr('fill', 'hsl(var(--clr-sec--400))') + .attr('stroke', 'none') .on('mouseover', (event, d) => { const tooltipContent = ( <div> @@ -87,8 +93,6 @@ export const BarPlot = ({ typeBarPlot: typeBarplot, numBins, data }: BarPlotProp ); setTooltipData({ x: event.pageX + 10, y: event.pageY - 28, content: tooltipContent }); - - //console.log('mouseover'); }) .on('mousemove', (event, d) => { if (tooltipData) { @@ -103,13 +107,10 @@ export const BarPlot = ({ typeBarPlot: typeBarplot, numBins, data }: BarPlotProp } return prevTooltipData; }); - }, 16); // Delay in milliseconds (adjust as needed) + }, 16); } - - //console.log('mousemove ', event); }) .on('mouseleave', (d) => { - //console.log('mouseleave'); setTooltipData(null); }); @@ -118,6 +119,23 @@ export const BarPlot = ({ typeBarPlot: typeBarplot, numBins, data }: BarPlotProp // Barplot for numerical data } else { + let marginPercentage = { + top: 0.19, + right: 0.17, + bottom: 0.19, + left: 0.4, + }; + let margin = { + top: marginPercentage.top * heightSVG, + right: marginPercentage.right * widthSVG, + bottom: marginPercentage.bottom * heightSVG, + left: marginPercentage.left * widthSVG, + }; + + groupMargin = svgPlot.append('g').attr('transform', `translate(${margin.left},${margin.top})`); + widthSVGwithinMargin = widthSVG - margin.left - margin.right; + heightSVGwithinMargin = heightSVG - margin.top - margin.bottom; + //console.log(typeBarplot, data); const dataCount = data.map((obj) => obj.count); const extentData = d3.extent(dataCount); @@ -144,7 +162,7 @@ export const BarPlot = ({ typeBarPlot: typeBarplot, numBins, data }: BarPlotProp .thresholds(rangeTH); const bins = histogram(dataCount); - + //console.log(bins); const extentBins: [number, number] = d3.extent(bins, (d) => d.length) as [number, number]; const yScale = d3 @@ -159,16 +177,31 @@ export const BarPlot = ({ typeBarPlot: typeBarplot, numBins, data }: BarPlotProp .enter() .append('rect') .attr('x', 0) - .attr('class', 'fill-primary stroke-white') + .attr('stroke', 'none') + .attr('fill', 'hsl(var(--clr-sec--400))') .attr('transform', (d) => 'translate(' + xScale(d.x0 || 0) + ',' + yScale(d.length) + ')') .attr('width', (d) => xScale(d.x1 || 0) - xScale(d.x0 || 0)) .attr('height', (d) => heightSVGwithinMargin - yScale(d.length)); const xAxis = d3 .axisBottom(xScale) - .tickValues([min, Math.round((min + max) / 2.0), max]) + //.tickValues([Math.round((min + max) / 2.0), max]) + .tickValues([max]) .tickFormat(d3.format('.2s')); + let xAxis2: d3.Axis<d3.NumberValue>; + + if (min < 10) { + xAxis2 = d3.axisBottom(xScale).tickValues([min]).tickFormat(d3.format('d')); + } else { + xAxis2 = d3.axisBottom(xScale).tickValues([min]).tickFormat(d3.format('.2s')); + } + + groupMargin + .append('g') + .attr('transform', 'translate(0,' + heightSVGwithinMargin + ')') + .call(xAxis2); + groupMargin .append('g') .attr('transform', 'translate(0,' + heightSVGwithinMargin + ')') @@ -184,12 +217,14 @@ export const BarPlot = ({ typeBarPlot: typeBarplot, numBins, data }: BarPlotProp } else { yAxis2 = d3.axisLeft(yScale).tickValues([extentBins[1]]).tickFormat(d3.format('.2s')); } - + // two axis for each number, it solves the 0.0 problem with ".2s" notatation groupMargin.append('g').call(yAxis1); groupMargin.append('g').call(yAxis2); } - svgPlot.selectAll('.tick text').attr('class', 'font-mono text-primary font-semibold').style('stroke', 'none'); + svgPlot.selectAll('.domain').style('stroke', 'hsl(var(--clr-sec--400))'); + svgPlot.selectAll('.tick line').style('stroke', 'hsl(var(--clr-sec--400))'); + svgPlot.selectAll('.tick text').attr('class', 'font-mono').style('stroke', 'none').style('fill', 'hsl(var(--clr-sec--500))'); groupMargin .append('rect') @@ -199,17 +234,9 @@ export const BarPlot = ({ typeBarPlot: typeBarplot, numBins, data }: BarPlotProp .attr('height', heightSVGwithinMargin) .attr('rx', 0) .attr('ry', 0) - .attr('class', 'fill-none stroke-secondary'); - - svgPlot - .append('rect') - .attr('x', 0.0) - .attr('y', 0.0) - .attr('width', widthSVG) - .attr('height', heightSVG) - .attr('rx', 0) - .attr('ry', 0) - .attr('class', 'fill-none stroke-secondary'); + .attr('stroke-width', 1) + .attr('fill', 'none') + .attr('stroke', 'hsl(var(--clr-sec--400))'); }, [data]); return ( @@ -219,8 +246,8 @@ export const BarPlot = ({ typeBarPlot: typeBarplot, numBins, data }: BarPlotProp className="container" width="100%" height="100%" - preserveAspectRatio="xMidYMid meet" - viewBox={`0 0 ${dimensions.width} ${dimensions.height}`} + //preserveAspectRatio="xMidYMid meet" + //viewBox={`0 0 ${dimensions.width} ${dimensions.height}`} ></svg> {tooltipData && <BarplotTooltip x={tooltipData.x} y={tooltipData.y} content={tooltipData.content} />} </div> diff --git a/libs/shared/lib/mock-data/query-result/typesMockQueryResults.ts b/libs/shared/lib/mock-data/query-result/typesMockQueryResults.ts index dc7a7765b69116d0c63f5d3a394b2b18b8ee6741..6286f5ae880970288418ee59492f8c4057e7781e 100644 --- a/libs/shared/lib/mock-data/query-result/typesMockQueryResults.ts +++ b/libs/shared/lib/mock-data/query-result/typesMockQueryResults.ts @@ -11,190 +11,189 @@ /** Mock elements used for testing the query results. */ export const typesMockQueryResults = { - edges: [ - { - from: 'worker/1', - id: 'onderdeel_van/1100', - _key: '1100', - _rev: '_cYl_jTO--O', - to: 'worker/3', - attributes: {}, + edges: [ + { + from: 'worker/1', + id: 'onderdeel_van/1100', + _key: '1100', + _rev: '_cYl_jTO--O', + to: 'worker/3', + attributes: {}, + }, + { + from: 'worker/3', + id: 'onderdeel_van/662', + _key: '662', + _rev: '_cYl_jR2--G', + to: 'worker/5', + attributes: {}, + }, + ], + nodes: [ + { + id: 'worker/1', + _key: '1', + _rev: '_cYl-Qmq-_H', + attributes: { + name: 'John Doe', + age: 30, + height: 175.5, + sacked: false, + birthdate: '1992-03-15', + startingSchedule: '08:00:00', + commutingDuration: 'PT1H', + firstLogin: '2023-12-20T09:30:00Z', }, - { - from: 'worker/3', - id: 'onderdeel_van/662', - _key: '662', - _rev: '_cYl_jR2--G', - to: 'worker/5', - attributes: {}, + }, + { + id: 'worker/2', + _key: '2', + _rev: '_cYl-Qmq--5', + attributes: { + name: 'Bob Johnson', + age: 35, + height: 180.2, + sacked: false, + birthdate: '1987-06-10', + startingSchedule: '07:45:00', + commutingDuration: 'PT1H15M', + firstLogin: '2023-12-18T10:00:00Z', }, - ], - nodes: [ - { - id: 'worker/1', - _key: '1', - _rev: '_cYl-Qmq-_H', - attributes: { - name: 'John Doe', - age: 30, - height: 175.5, - sacked: false, - birthdate: '1992-03-15', - startingSchedule: '08:00:00', - commutingDuration: 'PT1H', - firstLogin: '2023-12-20T09:30:00Z', - }, + }, + { + id: 'worker/3', + _key: '3', + _rev: '_cYl-Qmq--3', + attributes: { + name: 'Jane Smith', + age: 28, + height: 162.0, + sacked: true, + birthdate: '1995-08-20', + startingSchedule: '09:30:00', + commutingDuration: 'PT45M', + firstLogin: '2023-12-19T08:45:00Z', }, - { - id: 'worker/2', - _key: '2', - _rev: '_cYl-Qmq--5', - attributes: { - name: 'Bob Johnson', - age: 35, - height: 180.2, - sacked: false, - birthdate: '1987-06-10', - startingSchedule: '07:45:00', - commutingDuration: 'PT1H15M', - firstLogin: '2023-12-18T10:00:00Z', - }, + }, + { + id: 'worker/4', + _key: '4', + _rev: '_cYl-Qmq--z', + attributes: { + name: 'Bob Knee', + age: 35, + height: 180.2, + sacked: false, + birthdate: '1987-06-10', + startingSchedule: '07:45:00', + commutingDuration: 'PT1H15M', + firstLogin: '2023-10-18T10:00:00Z', }, - { - id: 'worker/3', - _key: '3', - _rev: '_cYl-Qmq--3', - attributes: { - name: 'Jane Smith', - age: 28, - height: 162.0, - sacked: true, - birthdate: '1995-08-20', - startingSchedule: '09:30:00', - commutingDuration: 'PT45M', - firstLogin: '2023-12-19T08:45:00Z', - }, + }, + { + id: 'worker/5', + _key: '5', + _rev: '_cYl-Qmq--x', + attributes: { + name: 'Alice Brown', + age: 25, + height: 155.8, + sacked: true, + birthdate: '1998-01-05', + startingSchedule: '08:30:00', + commutingDuration: 'PT30M', + firstLogin: '2023-12-17T11:15:00Z', }, - { - id: 'worker/4', - _key: '4', - _rev: '_cYl-Qmq--z', - attributes: { - name: 'Bob Knee', - age: 35, - height: 180.2, - sacked: false, - birthdate: '1987-06-10', - startingSchedule: '07:45:00', - commutingDuration: 'PT1H15M', - firstLogin: '2023-10-18T10:00:00Z', - }, + }, + { + id: 'worker/6', + _key: '6', + _rev: '_cYl-Qmq--v', + attributes: { + name: 'Michael Davis', + age: 40, + height: 170.0, + sacked: false, + birthdate: '1982-11-25', + startingSchedule: '08:15:00', + commutingDuration: 'PT1H30M', + firstLogin: '2023-12-16T12:30:00Z', }, - { - id: 'worker/5', - _key: '5', - _rev: '_cYl-Qmq--x', - attributes: { - name: 'Alice Brown', - age: 25, - height: 155.8, - sacked: true, - birthdate: '1998-01-05', - startingSchedule: '08:30:00', - commutingDuration: 'PT30M', - firstLogin: '2023-12-17T11:15:00Z', - }, + }, + { + id: 'worker/7', + _key: '7', + _rev: '_cYl-Qmq--p', + attributes: { + name: 'Sophia Turner', + age: 27, + height: 168.5, + sacked: false, + birthdate: '1994-07-08', + startingSchedule: '09:00:00', + commutingDuration: 'PT45M', + firstLogin: '2023-12-15T14:00:00Z', }, - { - id: 'worker/6', - _key: '6', - _rev: '_cYl-Qmq--v', - attributes: { - name: 'Michael Davis', - age: 40, - height: 170.0, - sacked: false, - birthdate: '1982-11-25', - startingSchedule: '08:15:00', - commutingDuration: 'PT1H30M', - firstLogin: '2023-12-16T12:30:00Z', - }, + }, + { + id: 'worker/8', + _key: '8', + _rev: '_cYl-Qmq--n', + attributes: { + name: 'David Miller', + age: 34, + height: 185.0, + sacked: true, + birthdate: '1989-04-30', + startingSchedule: '07:30:00', + commutingDuration: 'PT1H', + firstLogin: '2023-12-14T15:15:00Z', }, - { - id: 'worker/7', - _key: '7', - _rev: '_cYl-Qmq--p', - attributes: { - name: 'Sophia Turner', - age: 27, - height: 168.5, - sacked: false, - birthdate: '1994-07-08', - startingSchedule: '09:00:00', - commutingDuration: 'PT45M', - firstLogin: '2023-12-15T14:00:00Z', - }, + }, + { + id: 'worker/9', + _key: '9', + _rev: '_cYl-Qmq--l', + attributes: { + name: 'Olivia Harris', + age: 31, + height: 160.5, + sacked: false, + birthdate: '1992-12-15', + startingSchedule: '09:45:00', + commutingDuration: 'PT1H15M', + firstLogin: '2023-12-13T16:30:00Z', }, - { - id: 'worker/8', - _key: '8', - _rev: '_cYl-Qmq--n', - attributes: { - name: 'David Miller', - age: 34, - height: 185.0, - sacked: true, - birthdate: '1989-04-30', - startingSchedule: '07:30:00', - commutingDuration: 'PT1H', - firstLogin: '2023-12-14T15:15:00Z', - }, + }, + { + id: 'worker/10', + _key: '10', + _rev: '_cYl-Qmq--j', + attributes: { + name: 'Daniel Lee', + age: 29, + height: 175.0, + sacked: true, + birthdate: '1994-09-20', + startingSchedule: '08:00:00', + commutingDuration: 'PT30M', + firstLogin: '2023-12-12T17:45:00Z', }, - { - id: 'worker/9', - _key: '9', - _rev: '_cYl-Qmq--l', - attributes: { - name: 'Olivia Harris', - age: 31, - height: 160.5, - sacked: false, - birthdate: '1992-12-15', - startingSchedule: '09:45:00', - commutingDuration: 'PT1H15M', - firstLogin: '2023-12-13T16:30:00Z', - }, + }, + { + id: 'worker/11', + _key: '11', + _rev: '_cYl-Qmq--f', + attributes: { + name: 'Emily White', + age: 33, + height: 163.5, + sacked: false, + birthdate: '1989-06-18', + startingSchedule: '08:45:00', + commutingDuration: 'PT1H30M', + firstLogin: '2023-12-11T19:00:00Z', }, - { - id: 'worker/10', - _key: '10', - _rev: '_cYl-Qmq--j', - attributes: { - name: 'Daniel Lee', - age: 29, - height: 175.0, - sacked: true, - birthdate: '1994-09-20', - startingSchedule: '08:00:00', - commutingDuration: 'PT30M', - firstLogin: '2023-12-12T17:45:00Z', - }, - }, - { - id: 'worker/11', - _key: '11', - _rev: '_cYl-Qmq--f', - attributes: { - name: 'Emily White', - age: 33, - height: 163.5, - sacked: false, - birthdate: '1989-06-18', - startingSchedule: '08:45:00', - commutingDuration: 'PT1H30M', - firstLogin: '2023-12-11T19:00:00Z', - }, - }, - ], - }; - \ No newline at end of file + }, + ], +}; diff --git a/libs/shared/lib/vis/visualizations/table_vis/components/table.module.scss.d.ts b/libs/shared/lib/vis/visualizations/table_vis/components/table.module.scss.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..b903d499249203bbb3e30837d6c79462450d311d --- /dev/null +++ b/libs/shared/lib/vis/visualizations/table_vis/components/table.module.scss.d.ts @@ -0,0 +1,6 @@ + +declare const classNames: { + readonly 'table-container': 'table-container'; + readonly table: 'table'; +}; +export = classNames; diff --git a/libs/shared/lib/vis/visualizations/tablevis/components/Table.tsx b/libs/shared/lib/vis/visualizations/tablevis/components/Table.tsx index e9c21bc0be7e2da9539fcadb6e0686dcc0431748..c4474068698226562546502c4d8a940d7c7a8fe5 100644 --- a/libs/shared/lib/vis/visualizations/tablevis/components/Table.tsx +++ b/libs/shared/lib/vis/visualizations/tablevis/components/Table.tsx @@ -1,4 +1,5 @@ -import React, { useState, useEffect, useMemo } from 'react'; +import React, { useState, useEffect, useMemo, useRef } from 'react'; +import * as d3 from 'd3'; import Pagination from '@graphpolaris/shared/lib/components/pagination'; import { BarPlot } from '@graphpolaris/shared/lib/components/charts/barplot'; @@ -11,7 +12,7 @@ import { group } from 'd3'; export type AugmentedNodeAttributes = { attribute: NodeAttributes; type: Record<string, SchemaAttributeTypes> }; -type TableProps = { +export type TableProps = { data: AugmentedNodeAttributes[]; itemsPerPage: number; showBarPlot: boolean; @@ -24,8 +25,10 @@ type Data2RenderI = { showBarPlot?: boolean; }; +const THRESHOLD_WIDTH = 100; + export const Table = ({ data, itemsPerPage, showBarPlot }: TableProps) => { - const maxUniqueValues = 69; + const maxUniqueValues = 29; const barPlotNumBins = 10; const fetchAttributes = 0; @@ -39,9 +42,31 @@ export const Table = ({ data, itemsPerPage, showBarPlot }: TableProps) => { currentData: AugmentedNodeAttributes[]; } | null>(null); const [data2Render, setData2Render] = useState<Data2RenderI[]>([]); + //const dataColumns = useMemo(() => Object.keys(data[0].attribute), [data]); + const dataColumns = useMemo(() => Object.keys(data[data.length > fetchAttributes ? fetchAttributes : 0].attribute), [data]); const totalPages = Math.ceil(sortedData.length / itemsPerPage); + const [columnWidths, setColumnWidths] = useState<number[]>([]); + const thRefs = useRef<(HTMLTableCellElement | null)[]>([]); + + useEffect(() => { + const timeoutId = setTimeout(() => { + const widths = thRefs.current + .filter((ref) => ref !== null) + .map((ref) => { + if (ref) { + return ref.getBoundingClientRect().width; + } + return 0; + }); + setColumnWidths(widths); + console.log(widths); + }, 100); + + return () => clearTimeout(timeoutId); + }, [thRefs.current]); + useEffect(() => { if (sortColumn !== null) { const sorted = [...data].sort((a, b) => { @@ -200,9 +225,10 @@ export const Table = ({ data, itemsPerPage, showBarPlot }: TableProps) => { <th className="border-light group hover:bg-secondary-300 bg-secondary-200 text-left overflow-x-hidden truncate capitalize cursor-pointer" key={index + item} + ref={(el) => (thRefs.current[index] = el)} onClick={() => toggleSort(item)} > - <div className="flex flex-row max-w-full gap-1 justify-between"> + <div className="flex flex-row max-w-full gap-1 justify-between p-1"> <span className="shrink overflow-hidden text-ellipsis">{item}</span> <div className={ @@ -222,19 +248,21 @@ export const Table = ({ data, itemsPerPage, showBarPlot }: TableProps) => { <tr> {dataColumns.map((item, index) => ( <th className="border-light bg-light" key={index}> - <div className="th" key={index + item}> + <div className="th p-0" key={index + item}> <div className="h-full w-full overflow-hidden"> - {data2Render[index].showBarPlot && - (data2Render[index]?.typeData === 'int' || data2Render[index]?.typeData === 'float') ? ( - <BarPlot typeBarPlot="numerical" numBins={barPlotNumBins} data={data2Render[index].data} /> - ) : !data2Render[index]?.typeData || !data2Render[index].showBarPlot ? ( - <div className="font-normal mx-auto flex flex-row items-start justify-center w-full gap-1 text-center text-secondary-700"> - <span className="text-2xs overflow-x-hidden truncate">Unique values:</span> - <span className="text-xs shrink-0">{data2Render[index]?.numElements}</span> - </div> - ) : ( - <BarPlot typeBarPlot="categorical" numBins={barPlotNumBins} data={data2Render[index].data} /> - )} + {data2Render[index] && + (showBarPlot && data2Render[index].showBarPlot && columnWidths[index] > THRESHOLD_WIDTH ? ( + data2Render[index]?.typeData === 'int' || data2Render[index]?.typeData === 'float' ? ( + <BarPlot typeBarPlot="numerical" numBins={barPlotNumBins} data={data2Render[index].data} /> + ) : ( + <BarPlot typeBarPlot="categorical" numBins={barPlotNumBins} data={data2Render[index].data} /> + ) + ) : ( + <div className="font-normal mx-auto flex flex-row items-start justify-center w-full gap-1 text-center text-secondary-700 p-1"> + <span className="text-2xs overflow-x-hidden truncate">Unique values:</span> + <span className="text-xs shrink-0">{data2Render[index]?.numElements}</span> + </div> + ))} </div> </div> </th> @@ -246,7 +274,10 @@ export const Table = ({ data, itemsPerPage, showBarPlot }: TableProps) => { <tr key={rowIndex}> {dataColumns.map((col, colIndex) => ( <td className="border-light" data-datatype={item.type[col]} key={colIndex}> - {item.attribute[col] !== undefined ? (item.attribute[col] as any).toString() : item.attribute[col]} + {item.attribute[col] !== undefined && + (typeof item.attribute[col] !== 'object' || Array.isArray(item.attribute[col])) + ? (item.attribute[col] as any).toString() + : ' '} </td> ))} </tr> diff --git a/libs/shared/lib/vis/visualizations/tablevis/components/table.module.scss b/libs/shared/lib/vis/visualizations/tablevis/components/table.module.scss index 3c837c6367667bb967424608fd3799375757f286..d1447b756813fe0faef1c5d360de8e5937473c01 100644 --- a/libs/shared/lib/vis/visualizations/tablevis/components/table.module.scss +++ b/libs/shared/lib/vis/visualizations/tablevis/components/table.module.scss @@ -7,7 +7,7 @@ tr { @apply p-0 border-0; th { - @apply px-1.5 py-1.5 font-semibold; + @apply px-0 py-0 font-semibold; } } }