From 4e1703746213c802ae0818de89df56ae371f8e64 Mon Sep 17 00:00:00 2001 From: Marcos Pieras <pieras.marcos@gmail.com> Date: Thu, 11 Jul 2024 17:33:20 +0000 Subject: [PATCH] fix: stories and updates table and barplot for non data --- .../components/DesignGuides/styleGuide.mdx | 9 +- .../lib/components/buttons/overview.mdx | 90 +++-- .../charts/barplot/barplot.stories.tsx | 9 +- .../lib/components/charts/barplot/index.tsx | 334 +++++++----------- .../components/charts/scatterplot/index.tsx | 35 -- .../scatterplot/scatterplot.stories.tsx | 23 -- libs/shared/lib/components/icon/overview.mdx | 16 +- .../shared/lib/components/inputs/overview.mdx | 4 +- .../lib/components/pagination/overview.mdx | 2 +- .../lib/components/tooltip/Overview.mdx | 36 ++ libs/shared/lib/components/tooltip/index.tsx | 2 + .../components/tooltip/tooltip.stories.tsx | 2 +- .../query-result/typesMockQueryResults.json | 174 ++++++++- .../nodelinkvis/components/query2NL.tsx | 20 +- .../tablevis/components/Table.tsx | 71 ++-- .../tablevis/components/table.module.scss | 6 + .../tablevis/tablevis.stories.tsx | 19 +- .../vis/visualizations/tablevis/tablevis.tsx | 92 +++-- 18 files changed, 557 insertions(+), 387 deletions(-) delete mode 100644 libs/shared/lib/components/charts/scatterplot/index.tsx delete mode 100644 libs/shared/lib/components/charts/scatterplot/scatterplot.stories.tsx create mode 100644 libs/shared/lib/components/tooltip/Overview.mdx diff --git a/libs/shared/lib/components/DesignGuides/styleGuide.mdx b/libs/shared/lib/components/DesignGuides/styleGuide.mdx index a7e82a621..f461cac58 100644 --- a/libs/shared/lib/components/DesignGuides/styleGuide.mdx +++ b/libs/shared/lib/components/DesignGuides/styleGuide.mdx @@ -35,6 +35,7 @@ import { KeyboardArrowUp as KeyboardArrowUpIcon, CheckCircle as CheckCircleIcon, } from '@mui/icons-material'; +import { ArrowBack } from '@mui/icons-material'; <Meta title="Design guide" /> @@ -641,12 +642,12 @@ this is a hsl color, if you want to use this color in a css file you can use the GraphPolaris uses [Material UI](https://mui.com/material-ui/material-icons/) through an Icon component, where the Material UI icon is specified along its size: <div className="w-32 m-5 mx-auto"> - <Icon name="ArrowBack" size={32} /> + <Icon component={<ArrowBack />} size={32} /> </div> ```jsx -import { Icon } from '@graphpolaris/shared/lib/components/icon'; -<Icon name="ArrowBack" size={32} />; +import Icon from '@graphpolaris/shared/lib/components/icon'; +<Icon component={<ArrowBack />} size={32} />; ``` There are 5 types of Icons in Material UI: @@ -751,9 +752,7 @@ We ditinguish two usage scenarios: iconName="ArrowForward" iconPosition="leading" onClick={() => { - // Add your code to handle the button click event here console.log('Button clicked!'); - // You can perform any desired actions or state updates here }} /> </div> diff --git a/libs/shared/lib/components/buttons/overview.mdx b/libs/shared/lib/components/buttons/overview.mdx index 810e45683..9fba35c6f 100644 --- a/libs/shared/lib/components/buttons/overview.mdx +++ b/libs/shared/lib/components/buttons/overview.mdx @@ -11,66 +11,60 @@ A button is used a lot in GP. [//]: # '<Canvas of={ButtonStories.Primary} />' -<Canvas> - <Story id="components-button--primary" name="Primary Button"> - {ButtonStories.Primary.args} - </Story> - <Story id="components-button--secondary" name="Secondary Button"> - {ButtonStories.Secondary.args} - </Story> - <Story id="components-button--danger" name="Danger Button"> - {ButtonStories.Danger.args} - </Story> -</Canvas> - -<Canvas> - <Story id="components-button--solid" name="Solid Button"> - {ButtonStories.Solid.args} - </Story> - <Story id="components-button--outline" name="Outline Button"> - {ButtonStories.Outline.args} - </Story> - <Story id="components-button--ghost" name="Ghost Button"> - {ButtonStories.Ghost.args} - </Story> -</Canvas> - -<Canvas> - <Story id="components-button--icon-button" name="Icon Button"> - {ButtonStories.IconButton.args} - </Story> -</Canvas> - <div className="grid grid-cols-2 gap-4"> <div className="flex flex-col gap-3"> - <Button type="primary" variant="solid" label="Click me" onClick={() => alert('Button clicked')} /> - <Button type="primary" variant="outline" label="Click me" onClick={() => alert('Button clicked')} /> - <Button type="primary" variant="ghost" label="Click me" onClick={() => alert('Button clicked')} /> + <Button variantType="primary" variant="solid" label="Click me" onClick={() => alert('Button clicked')} /> + <Button variantType="primary" variant="outline" label="Click me" onClick={() => alert('Button clicked')} /> + <Button variantType="primary" variant="ghost" label="Click me" onClick={() => alert('Button clicked')} /> </div> <div className="flex flex-col gap-3"> - <Button type="secondary" variant="solid" label="Click me" onClick={() => alert('Button clicked')} /> - <Button type="secondary" variant="outline" label="Click me" onClick={() => alert('Button clicked')} /> - <Button type="secondary" variant="ghost" label="Click me" onClick={() => alert('Button clicked')} /> + <Button variantType="secondary" variant="solid" label="Click me" onClick={() => alert('Button clicked')} /> + <Button variantType="secondary" variant="outline" label="Click me" onClick={() => alert('Button clicked')} /> + <Button variantType="secondary" variant="ghost" label="Click me" onClick={() => alert('Button clicked')} /> </div> <div className="flex flex-col gap-3"> - <Button type="danger" variant="solid" label="Click me" onClick={() => alert('Button clicked')} /> - <Button type="danger" variant="outline" label="Click me" onClick={() => alert('Button clicked')} /> - <Button type="danger" variant="ghost" label="Click me" onClick={() => alert('Button clicked')} /> + <Button variantType="danger" variant="solid" label="Click me" onClick={() => alert('Button clicked')} /> + <Button variantType="danger" variant="outline" label="Click me" onClick={() => alert('Button clicked')} /> + <Button variantType="danger" variant="ghost" label="Click me" onClick={() => alert('Button clicked')} /> </div> <div className="flex flex-col gap-3"> - <Button type="primary" variant="solid" label="Click me" onClick={() => alert('Button clicked')} disabled /> - <Button type="secondary" variant="outline" label="Click me" onClick={() => alert('Button clicked')} disabled /> - <Button type="danger" variant="ghost" label="Click me" onClick={() => alert('Button clicked')} disabled /> + <Button variantType="primary" variant="solid" label="Click me" onClick={() => alert('Button clicked')} disabled /> + <Button variantType="secondary" variant="outline" label="Click me" onClick={() => alert('Button clicked')} disabled /> + <Button variantType="danger" variant="ghost" label="Click me" onClick={() => alert('Button clicked')} disabled /> </div> <div className="flex flex-row gap-3 col-span-2"> - <Button type="primary" size="lg" label="Click me" iconName="NavigateBefore" onClick={() => alert('Button clicked')} /> - <Button type="primary" size="md" label="Click me" iconName="ArrowForward" iconPlacement="trailing" onClick={() => alert('Button clicked')} /> - <Button type="danger" size="sm" label="Click me" iconName="DeleteOutline" onClick={() => alert('Button clicked')} /> - <Button type="primary" size="xs" label="Click me" rounded onClick={() => alert('Button clicked')} /> + <Button variantType="primary" size="lg" label="Click me" iconName="NavigateBefore" onClick={() => alert('Button clicked')} /> + <Button variantType="primary" size="md" label="Click me" iconName="ArrowForward" iconPlacement="trailing" onClick={() => alert('Button clicked')} /> + <Button variantType="danger" size="sm" label="Click me" iconName="DeleteOutline" onClick={() => alert('Button clicked')} /> + <Button variantType="primary" size="xs" label="Click me" rounded onClick={() => alert('Button clicked')} /> </div> <div className="col-span-2"> - <Button block type="primary" size="md" label="Click me" onClick={() => alert('Button clicked')} /> + <Button block variantType="primary" size="md" label="Click me" onClick={() => alert('Button clicked')} /> </div> </div> -variantTypevariantTypevariantTypevariantTypevariantTypevariantTypevariantTypevariantTypevariantTypevariantTypevariantTypevariantTypevariantTypevariantTypevariantTypevariantTypevariantType + +## Usage + +<div className="my-10 col-span-2"> + <Button + block + variantType="primary" + size="md" + label="Click me" + iconName="ArrowForward" + iconPlacement="trailing" + onClick={() => alert('Button clicked')} + /> +</div> + +```jsx +<Button + variantType="primary" + size="md" + label="Click me" + iconName="ArrowForward" + iconPlacement="trailing" + onClick={() => alert('Button clicked')} +/> +``` diff --git a/libs/shared/lib/components/charts/barplot/barplot.stories.tsx b/libs/shared/lib/components/charts/barplot/barplot.stories.tsx index c7c7da834..a63d88829 100644 --- a/libs/shared/lib/components/charts/barplot/barplot.stories.tsx +++ b/libs/shared/lib/components/charts/barplot/barplot.stories.tsx @@ -15,12 +15,15 @@ export const CategoricalData = { args: { data: [ { category: 'Category A', count: 250 }, + { category: 'NoData', count: 100 }, { category: 'Category B', count: 20 }, { category: 'Category C', count: 15 }, ], numBins: 5, typeBarPlot: 'categorical', - axis: true, + marginPercentage: { top: 0.1, right: 0, left: 0, bottom: 0 }, + axis: false, + name: 'mock1', }, }; @@ -34,7 +37,9 @@ export const NumericalData = { { category: 'Data Point 5', count: 8 }, ], numBins: 5, + marginPercentage: { top: 0.1, right: 0, left: 0, bottom: 0 }, typeBarPlot: 'numerical', - axis: true, + axis: false, + name: 'mock2', }, }; diff --git a/libs/shared/lib/components/charts/barplot/index.tsx b/libs/shared/lib/components/charts/barplot/index.tsx index 97ecf9f26..ba038b94e 100644 --- a/libs/shared/lib/components/charts/barplot/index.tsx +++ b/libs/shared/lib/components/charts/barplot/index.tsx @@ -1,21 +1,6 @@ -import React, { LegacyRef, useEffect, useRef, useState } from 'react'; -import { BarPlotTooltip } from '@graphpolaris/shared/lib/components/tooltip'; -import { - Axis, - Bin, - NumberValue, - axisBottom, - axisLeft, - bin, - extent, - format, - max, - range, - scaleBand, - scaleLinear, - select, - Selection, -} from 'd3'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; +import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from '@graphpolaris/shared/lib/components/tooltip'; +import { axisLeft, bin, extent, format, max, range, scaleBand, scaleLinear, select } from 'd3'; export type BarPlotProps = { data: { category: string; count: number }[]; @@ -31,246 +16,173 @@ export type BarPlotProps = { maxBarsCount?: number; strokeWidth?: number; axis?: boolean; + name: string; }; -export const BarPlot = ({ typeBarPlot, numBins, data, marginPercentage, className, maxBarsCount, strokeWidth, axis }: BarPlotProps) => { +export const BarPlot = ({ typeBarPlot, numBins, data, marginPercentage, className, maxBarsCount, axis, name }: BarPlotProps) => { const svgRef = useRef<SVGSVGElement | null>(null); + const groupMarginRef = useRef<SVGGElement | null>(null); + const [dimensions, setDimensions] = useState({ width: 0, height: 0 }); - const [tooltipData, setTooltipData] = useState<{ x: number; y: number; content: { category: string; count: number } } | null>(null); useEffect(() => { - if (!svgRef.current) return; + function handleResize() { + if (svgRef.current) { + setDimensions({ width: svgRef.current.getBoundingClientRect().width, height: svgRef.current.getBoundingClientRect().height }); + } + } - const bounds = svgRef.current.getBoundingClientRect(); - const widthSVG: number = bounds.width; - const heightSVG: number = bounds.height; + window.addEventListener('resize', handleResize); + if (svgRef.current) new ResizeObserver(handleResize).observe(svgRef.current); - setDimensions({ width: widthSVG, height: heightSVG }); - const svgPlot = select(svgRef.current); + return () => { + window.removeEventListener('resize', handleResize); + }; + }, []); - svgPlot.selectAll('*').remove(); + const dataRects = useMemo(() => { + if (!svgRef.current || dimensions.width === 0 || dimensions.height === 0) return []; + const { width, height } = dimensions; + const widthSVG: number = width; + const heightSVG: number = height; - let groupMargin: Selection<SVGGElement, unknown, null, undefined>; - let widthSVGwithinMargin: number = 0.0; - let heightSVGwithinMargin: number = 0.0; + const svgPlot = select(svgRef.current); - // BarPlot for categorical data - if (typeBarPlot == 'categorical') { - if (!marginPercentage) - 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, + if (!marginPercentage) + 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, + }; + select(groupMarginRef.current).attr('transform', `translate(${margin.left},${margin.top})`); + let widthSVGwithinMargin = widthSVG - margin.left - margin.right; + let heightSVGwithinMargin = heightSVG - margin.top - margin.bottom; + + if (typeBarPlot === 'categorical') { + const defs = svgPlot.append('defs'); + + defs + .append('pattern') + .attr('id', 'diagonalHatch') + .attr('width', 6) + .attr('height', 6) + .attr('patternTransform', 'rotate(45 0 0)') + .attr('patternUnits', 'userSpaceOnUse') + .append('rect') + .attr('width', 2) + .attr('height', 6) + .attr('transform', 'translate(0,0)') + .attr('fill', '#cccccc'); - 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 dataTopCounts = dataSorted.filter((item, i) => !maxBarsCount || i < maxBarsCount); const maxCount = max(dataTopCounts, (d) => d.count) ?? 1; const xScale = scaleBand() .range([0, widthSVGwithinMargin]) - .domain( - dataTopCounts.map(function (d) { - return d.category; - }), - ) - .padding(0.2); + .domain(dataTopCounts.map((d) => d.category)) + .padding(0.1); - // send this const yScale = scaleLinear().domain([0, maxCount]).range([heightSVGwithinMargin, 0]); - // here out to axis component - const yAxis1 = axisLeft(yScale).tickValues([0]).tickFormat(format('d')); // to show 0 without decimanls - let yAxis2: Axis<NumberValue>; - + let yAxis2; if (maxCount < 10) { yAxis2 = axisLeft(yScale).tickValues([maxCount]).tickFormat(format('d')); } else { yAxis2 = axisLeft(yScale).tickValues([maxCount]).tickFormat(format('.2s')); } - groupMargin - .selectAll<SVGRectElement, Bin<string, number>>('barplotCats') - .data(dataTopCounts) - .enter() - .append('rect') - .attr('x', (d) => xScale(d.category) || 0) - .attr('y', (d) => yScale(d.count)) - .attr('width', xScale.bandwidth()) - .attr('height', (d) => heightSVGwithinMargin - yScale(d.count)) - .attr('class', 'hover:stroke-black hover:stroke-2 hover:stroke-secondary-400 fill-secondary-400') - .on('mouseover', (event, d) => { - setTooltipData({ x: event.layerX + 10, y: event.layerY - 28, content: d }); - }) - .on('mousemove', (event, d) => { - if (tooltipData) { - setTimeout(() => { - setTooltipData((prevTooltipData) => { - if (prevTooltipData) { - return { - ...prevTooltipData, - x: event.pageX + 10, - y: event.pageY - 28, - }; - } - return prevTooltipData; - }); - }, 16); - } - }) - .on('mouseleave', (d) => { - setTooltipData(null); - }); - - if (axis) { - groupMargin.append('g').call(yAxis1); - groupMargin.append('g').call(yAxis2); - } - - // Barplot for numerical data + const scaledData = dataTopCounts.map((d, i) => ({ + x: xScale(d.category) || 0, + y: yScale(d.count), + width: xScale.bandwidth(), + height: heightSVGwithinMargin - yScale(d.count), + category: d.category, + count: d.count, + key: `${name}-${i}`, + })); + + return scaledData; } else { - if (!marginPercentage) - 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; const dataCount = data.map((obj) => obj.count); const extentData = extent(dataCount); const [min, max] = extentData as [number, number]; - const xScale = scaleLinear().range([0, widthSVGwithinMargin]).domain([min, max]); //.nice(); - - // histogram -> deprecated - // On bin(), .thresholds(x.ticks(numBins)). Creates artifacts: not full rects on the plot - // CHECK THIS: https://dev.to/kevinlien/d3-histograms-and-fixing-the-bin-problem-4ac5 + const xScale = scaleLinear().range([0, widthSVGwithinMargin]).domain([min, max]); const rangeTH: number[] = range(min, max, (max - min) / numBins); - - if (rangeTH.length != numBins) { + if (rangeTH.length !== numBins) { rangeTH.pop(); } const histogram = bin() - .value(function (d) { - return d; - }) + .value((d) => d) .domain([min, max]) .thresholds(rangeTH); const bins = histogram(dataCount); - const extentBins: [number, number] = extent(bins, (d) => d.length) as [number, number]; - - const yScale = scaleLinear() - .range([heightSVGwithinMargin, 0]) - .domain([0, extentBins[1]] as [number, number]); - - groupMargin - .selectAll<SVGRectElement, Bin<number, number>>('barplotbins') - .data(bins) - .enter() - .append('rect') - .attr('x', 0) - .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 = axisBottom(xScale) - //.tickValues([Math.round((min + max) / 2.0), max]) - .tickValues([max]) - .tickFormat(format('.2s')); - - let xAxis2: Axis<NumberValue>; - - if (min < 10) { - xAxis2 = axisBottom(xScale).tickValues([min]).tickFormat(format('d')); - } else { - xAxis2 = axisBottom(xScale).tickValues([min]).tickFormat(format('.2s')); - } - - groupMargin - .append('g') - .attr('transform', 'translate(0,' + heightSVGwithinMargin + ')') - .call(xAxis2); - - groupMargin - .append('g') - .attr('transform', 'translate(0,' + heightSVGwithinMargin + ')') - .call(xAxis); - - const yAxis1 = axisLeft(yScale).tickValues([0]).tickFormat(format('d')); // to show 0 without decimanls - //const yAxis2 = axisLeft(yScale).tickValues([extentBins[1]]).tickFormat(format('.2s')); - - let yAxis2: Axis<NumberValue>; - - if (extentBins[1] < 10) { - yAxis2 = axisLeft(yScale).tickValues([extentBins[1]]).tickFormat(format('d')); - } else { - yAxis2 = axisLeft(yScale).tickValues([extentBins[1]]).tickFormat(format('.2s')); - } + const extentBins = extent(bins, (d) => d.length) as [number, number]; + + const yScale = scaleLinear().range([heightSVGwithinMargin, 0]).domain([0, extentBins[1]]); + + const scaledData = bins.map((d, i) => { + const x0 = d.x0 || 0; + const x1 = d.x1 || 0; + parseFloat(x1.toFixed(2)); + return { + x: xScale(x0), + y: yScale(d.length), + width: xScale(x1) - xScale(x0), + height: heightSVGwithinMargin - yScale(d.length), + category: `[${parseFloat(x0.toFixed(2))},${parseFloat(x1.toFixed(2))}]`, + count: d.length, + key: `${name}-${i}`, + }; + }); - if (axis) { - // two axis for each number, it solves the 0.0 problem with ".2s" notatation - groupMargin.append('g').call(yAxis1); - groupMargin.append('g').call(yAxis2); - } + return scaledData; } - - 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') - .attr('x', 0.0) - .attr('y', 0.0) - .attr('width', widthSVGwithinMargin) - .attr('height', heightSVGwithinMargin) - .attr('rx', 0) - .attr('ry', 0) - .attr('stroke-width', strokeWidth || 0) - .attr('fill', 'none') - .attr('stroke', 'hsl(var(--clr-sec--400))'); - }, [data, svgRef]); + }, [data, dimensions, marginPercentage, numBins, typeBarPlot, axis, name]); return ( - <div className={!!className ? className : ''}> - <svg ref={svgRef} className="" width="100%" height="100%"></svg> - {tooltipData && ( - <BarPlotTooltip x={tooltipData.x} y={tooltipData.y}> - <div> - <span>{tooltipData.content.category.toString()}</span>: <span>{tooltipData.content.count}</span> - </div> - </BarPlotTooltip> - )} - </div> + <TooltipProvider delayDuration={300}> + <div className={className || ''}> + <svg ref={svgRef} width="100%" height="100%"> + <g ref={groupMarginRef}> + {dataRects?.map((d) => ( + <Tooltip key={d.key}> + <TooltipTrigger asChild> + <rect + x={d.x} + y={d.y} + width={d.width} + height={d.height} + stroke={'hsl(var(--clr-sec--400))'} + strokeWidth={d.category === 'NoData' ? '0.2' : '0'} + fill={d.category === 'NoData' ? 'url(#diagonalHatch)' : 'hsl(var(--clr-sec--400))'} + className={'hover:fill-accent'} + /> + </TooltipTrigger> + <TooltipContent> + <div> + <strong>Category:</strong> {d.category} + <br /> + <strong>Count:</strong> {d.count} + </div> + </TooltipContent> + </Tooltip> + ))} + </g> + </svg> + </div> + </TooltipProvider> ); }; - -export default BarPlot; diff --git a/libs/shared/lib/components/charts/scatterplot/index.tsx b/libs/shared/lib/components/charts/scatterplot/index.tsx deleted file mode 100644 index d65989f00..000000000 --- a/libs/shared/lib/components/charts/scatterplot/index.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import React, { useEffect, useRef } from 'react'; -import * as d3 from 'd3'; - -export interface ScatterplotProps { - data: { x: number; y: number }[]; -} - -const Scatterplot: React.FC<ScatterplotProps> = ({ data }) => { - const svgRef = useRef<SVGSVGElement | null>(null); - - useEffect(() => { - if (svgRef.current) { - const svg = d3.select(svgRef.current); - - svg - .selectAll('circle') - .data(data) - .enter() - .append('circle') - .attr('cx', (d) => d.x * 30) - .attr('cy', (d) => 100 - d.y * 10) - .attr('r', 5) - .attr('fill', 'red'); - } - }, [data]); - - return ( - <div> - <h2>Scatterplot</h2> - <svg ref={svgRef} width={300} height={120}></svg> - </div> - ); -}; - -export default Scatterplot; diff --git a/libs/shared/lib/components/charts/scatterplot/scatterplot.stories.tsx b/libs/shared/lib/components/charts/scatterplot/scatterplot.stories.tsx deleted file mode 100644 index 271cda53d..000000000 --- a/libs/shared/lib/components/charts/scatterplot/scatterplot.stories.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; -import { Meta } from '@storybook/react'; -import Scatterplot, { ScatterplotProps } from '.'; - -const Component: Meta<typeof Scatterplot> = { - title: 'Visual charts/Charts/Scatterplot', - //tags: ['autodocs'], - component: Scatterplot, -}; - -export default Component; - -export const ScatterplotInput = { - args: { - data: [ - { x: 1, y: 3 }, - { x: 2, y: 7 }, - { x: 3, y: 2 }, - { x: 4, y: 5 }, - { x: 5, y: 8 }, - ], - }, -}; diff --git a/libs/shared/lib/components/icon/overview.mdx b/libs/shared/lib/components/icon/overview.mdx index d03354ff2..5dd08be20 100644 --- a/libs/shared/lib/components/icon/overview.mdx +++ b/libs/shared/lib/components/icon/overview.mdx @@ -1,6 +1,8 @@ import { Canvas, Meta, Story } from '@storybook/blocks'; import * as IconStories from './icon.stories'; -import { Icon } from '.'; +import { Icon } from '../icon'; + +import { ArrowBack } from '@mui/icons-material'; <Meta title="Components/Icon" component={Icon} /> @@ -11,8 +13,10 @@ The `Icon` component is used to display icons in the application in one of the f ## Usage -<Canvas> - <Story id="components-icon--default" name="Default Icon"> - {IconStories.Default.args} - </Story> -</Canvas> +<div className="w-52 m-5"> + <Icon component={<ArrowBack />} size={24} /> +</div> + +```jsx +<Icon component={<ArrowBack />} size={24} /> +``` diff --git a/libs/shared/lib/components/inputs/overview.mdx b/libs/shared/lib/components/inputs/overview.mdx index 8f26649d2..8f4c2df27 100644 --- a/libs/shared/lib/components/inputs/overview.mdx +++ b/libs/shared/lib/components/inputs/overview.mdx @@ -7,9 +7,9 @@ export const components = { <Meta title="Components/inputs" component={Input} /> -# Pagination +# Inputs -A pagination component for navigating through pages. +Different types of inputs: # Form Input Demo diff --git a/libs/shared/lib/components/pagination/overview.mdx b/libs/shared/lib/components/pagination/overview.mdx index 9401f95be..9804a7cb0 100644 --- a/libs/shared/lib/components/pagination/overview.mdx +++ b/libs/shared/lib/components/pagination/overview.mdx @@ -1,5 +1,5 @@ import { Canvas, Meta, Story } from '@storybook/blocks'; -import Pagination from '.'; +import { Pagination } from '.'; import * as PaginationStories from './pagination.stories'; <Meta title="Components/pagination" component={Pagination} /> diff --git a/libs/shared/lib/components/tooltip/Overview.mdx b/libs/shared/lib/components/tooltip/Overview.mdx new file mode 100644 index 000000000..369f13159 --- /dev/null +++ b/libs/shared/lib/components/tooltip/Overview.mdx @@ -0,0 +1,36 @@ +import { Canvas, Meta, Story } from '@storybook/blocks'; + +import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from './index'; + +<Meta title="Components/Tooltip" component={Tooltip} /> + +## Usage + +<div> + <TooltipProvider delayDuration={0}> + <div className="flex justify-center items-center py-16"> + <Tooltip> + <TooltipTrigger> + <div className="mx-auto">Hover over me</div> + </TooltipTrigger> + <TooltipContent side={'bottom'}> + <p>This is a tooltip</p> + </TooltipContent> + </Tooltip> + </div> + </TooltipProvider> +</div> + +```jsx + <div className="flex justify-center items-center py-16"> + <Tooltip> + <TooltipTrigger> + <div className="mx-auto">Hover over me</div> + </TooltipTrigger> + <TooltipContent side={"bottom"}> + <p>This is a tooltip</p> + </TooltipContent> + </Tooltip> + </div> + </TooltipProvider> +``` diff --git a/libs/shared/lib/components/tooltip/index.tsx b/libs/shared/lib/components/tooltip/index.tsx index 4024bdf57..126981134 100644 --- a/libs/shared/lib/components/tooltip/index.tsx +++ b/libs/shared/lib/components/tooltip/index.tsx @@ -2,6 +2,7 @@ export * from './Tooltip'; +/* export interface BarTooltipProps { x: number; y: number; @@ -18,3 +19,4 @@ export const BarPlotTooltip = ({ x, y, children }: BarTooltipProps) => { </div> ); }; +*/ diff --git a/libs/shared/lib/components/tooltip/tooltip.stories.tsx b/libs/shared/lib/components/tooltip/tooltip.stories.tsx index cdc1ad9aa..2bd6e90db 100644 --- a/libs/shared/lib/components/tooltip/tooltip.stories.tsx +++ b/libs/shared/lib/components/tooltip/tooltip.stories.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Meta } from '@storybook/react'; +import { Meta, StoryObj } from '@storybook/react'; import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from './index'; export default { diff --git a/libs/shared/lib/mock-data/query-result/typesMockQueryResults.json b/libs/shared/lib/mock-data/query-result/typesMockQueryResults.json index ad7e20722..5aa034ef2 100644 --- a/libs/shared/lib/mock-data/query-result/typesMockQueryResults.json +++ b/libs/shared/lib/mock-data/query-result/typesMockQueryResults.json @@ -1 +1,173 @@ -{"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"}},{"_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/3","_key":"3","_rev":"_cYl-Qmq--3","attributes":{"name":"Jane Smith","age":28,"height":162,"sacked":true,"birthdate":"1995-08-20","startingSchedule":"09:30:00","commutingDuration":"PT45M","firstLogin":"2023-12-19T08:45: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/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/6","_key":"6","_rev":"_cYl-Qmq--v","attributes":{"name":"Michael Davis","age":40,"height":170,"sacked":false,"birthdate":"1982-11-25","startingSchedule":"08:15:00","commutingDuration":"PT1H30M","firstLogin":"2023-12-16T12:30: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/8","_key":"8","_rev":"_cYl-Qmq--n","attributes":{"name":"David Miller","age":34,"height":185,"sacked":true,"birthdate":"1989-04-30","startingSchedule":"07:30:00","commutingDuration":"PT1H","firstLogin":"2023-12-14T15:15: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/10","_key":"10","_rev":"_cYl-Qmq--j","attributes":{"name":"Daniel Lee","age":29,"height":175,"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 +{ + "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" + } + }, + { + "_id": "worker/2", + "_key": "2", + "_rev": "_cYl-Qmq--5", + "attributes": { + "name": "Bob Johnson", + "age": 35, + "height": 180.2, + "sacked": false, + "birthdate": "", + "startingSchedule": "07:45:00", + "commutingDuration": "PT1H15M", + "firstLogin": "2023-12-18T10:00:00Z" + } + }, + { + "_id": "worker/3", + "_key": "3", + "_rev": "_cYl-Qmq--3", + "attributes": { + "name": "Jane Smith", + "age": 28, + "height": 162, + "sacked": true, + "birthdate": "1995-08-20", + "startingSchedule": "09:30:00", + "commutingDuration": "PT45M", + "firstLogin": "2023-12-19T08:45: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/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/6", + "_key": "6", + "_rev": "_cYl-Qmq--v", + "attributes": { + "name": "Michael Davis", + "age": 40, + "height": 170, + "sacked": false, + "birthdate": "1982-11-25", + "startingSchedule": "08:15:00", + "commutingDuration": "PT1H30M", + "firstLogin": "2023-12-16T12:30: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/8", + "_key": "8", + "_rev": "_cYl-Qmq--n", + "attributes": { + "name": "David Miller", + "age": 34, + "height": 185, + "sacked": true, + "birthdate": "1989-04-30", + "startingSchedule": "07:30:00", + "commutingDuration": "PT1H", + "firstLogin": "2023-12-14T15:15: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/10", + "_key": "10", + "_rev": "_cYl-Qmq--j", + "attributes": { + "name": "Daniel Lee", + "age": 29, + "height": 175, + "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": "", + "startingSchedule": "08:45:00", + "commutingDuration": "PT1H30M", + "firstLogin": "2023-12-11T19:00:00Z" + } + } + ] +} diff --git a/libs/shared/lib/vis/visualizations/nodelinkvis/components/query2NL.tsx b/libs/shared/lib/vis/visualizations/nodelinkvis/components/query2NL.tsx index ef0aba7b5..2c6d73ce6 100644 --- a/libs/shared/lib/vis/visualizations/nodelinkvis/components/query2NL.tsx +++ b/libs/shared/lib/vis/visualizations/nodelinkvis/components/query2NL.tsx @@ -156,11 +156,23 @@ export function parseQueryResult(queryResult: GraphQueryResult, ml: ML, options: let communityDetectionInResult = false; let shortestPathInResult = false; let linkPredictionInResult = false; - for (let i = 0; i < queryResult.nodes.length; i++) { // Assigns a group to every entity type for color coding const nodeId = queryResult.nodes[i]._id; - const entityType = queryResult.nodes[i].label; + // for datasets without label, label is included in id. eg. "kamerleden/112" + //const entityType = queryResult.nodes[i].label; + let entityType: string; + const node = queryResult.nodes[i]; + + if (node.label === undefined) { + if (node.attributes.labels !== undefined && Array.isArray(node.attributes.labels)) { + entityType = node.attributes.labels[0]; + } else { + entityType = node._id.split('/')[0]; + } + } else { + entityType = node.label; + } // The preferred text to be shown on top of the node let preferredText = nodeId; @@ -180,10 +192,12 @@ export function parseQueryResult(queryResult: GraphQueryResult, ml: ML, options: if (queryResult.nodes[i].attributes.label !== undefined) preferredText = queryResult.nodes[i].attributes.label as string; if (queryResult.nodes[i].attributes.naam !== undefined) preferredText = queryResult.nodes[i].attributes.naam as string; + //const labelTemplate = queryResult.nodes[i].label == undefined ? queryResult.nodes[i]._id.split('/')[0] : queryResult.nodes[i].label; + let radius = options.defaultRadius || 5; let data: NodeType = { _id: queryResult.nodes[i]._id, - label: queryResult.nodes[i].label, + label: entityType, attributes: queryResult.nodes[i].attributes, type: typeNumber, displayInfo: preferredText, diff --git a/libs/shared/lib/vis/visualizations/tablevis/components/Table.tsx b/libs/shared/lib/vis/visualizations/tablevis/components/Table.tsx index 0f9759061..365d3fe68 100644 --- a/libs/shared/lib/vis/visualizations/tablevis/components/Table.tsx +++ b/libs/shared/lib/vis/visualizations/tablevis/components/Table.tsx @@ -27,7 +27,8 @@ type Data2RenderI = { showBarPlot?: boolean; }; -const THRESHOLD_WIDTH = 100; +const THRESHOLD_WIDTH = 50; +const NODATASTRING = 'NoData'; export const Table = ({ data, itemsPerPage, showBarPlot, showAttributes, selectedEntity, maxBarsCount }: TableProps) => { const maxUniqueValues = 29; @@ -44,8 +45,11 @@ export const Table = ({ data, itemsPerPage, showBarPlot, showAttributes, selecte } | null>(null); const [data2Render, setData2Render] = useState<Data2RenderI[]>([]); const dataColumns = useMemo(() => { - if (showAttributes && showAttributes.length > 0) { - return showAttributes.filter((attr) => Object.keys(data[0].attribute).includes(attr)); + // sort to keep original order + const showAttributesCopy = [...showAttributes].sort((a, b) => a.localeCompare(b)); + + if (showAttributesCopy && showAttributesCopy.length > 0) { + return showAttributesCopy.filter((attr) => Object.keys(data[0].attribute).includes(attr)); } return Object.keys(data[0].attribute); }, [data, showAttributes, selectedEntity]); @@ -155,7 +159,6 @@ export const Table = ({ data, itemsPerPage, showBarPlot, showAttributes, selecte data: [], numElements: 0, }; - if ( firstRowData.type[dataColumn] === 'string' || firstRowData.type[dataColumn] === 'date' || @@ -163,7 +166,26 @@ export const Table = ({ data, itemsPerPage, showBarPlot, showAttributes, selecte firstRowData.type[dataColumn] === 'datetime' || firstRowData.type[dataColumn] === 'time' ) { - const groupedData = group(data, (d) => d.attribute[dataColumn]); + const groupedData = group(data, (d) => { + const value = d.attribute[dataColumn]; + + if (typeof value === 'object' && value !== null) { + if (Object.keys(value).length === 0) { + return NODATASTRING; + } else { + return value; + } + } else { + if (value === undefined || value === '') { + return NODATASTRING; + } else if (Array.isArray(value)) { + return value.toString(); + } else { + return value; + } + } + }); + categoryCounts = Array.from(groupedData, ([category, items]) => ({ category: category as string, count: items.length, @@ -177,19 +199,7 @@ export const Table = ({ data, itemsPerPage, showBarPlot, showAttributes, selecte newData2Render.data = categoryCounts; newData2Render.showBarPlot = true; } - } else if (firstRowData.type[dataColumn] === 'bool') { - const groupedData = group(data, (d) => d.attribute[dataColumn]); - - categoryCounts = Array.from(groupedData, ([category, items]) => ({ - category: category as string, - count: items.length, - })); - - newData2Render.numElements = categoryCounts.length; - newData2Render.data = categoryCounts; - newData2Render.showBarPlot = true; - - // number: float and int + // boolean } else if (firstRowData.type[dataColumn] === 'bool') { const groupedData = group(data, (d) => d.attribute[dataColumn]); @@ -228,8 +238,10 @@ export const Table = ({ data, itemsPerPage, showBarPlot, showAttributes, selecte return newData2Render; }); - setData2Render(_data2Render); - }, [currentPage, data, sortedData, selectedEntity, maxBarsCount]); + const _data2RenderSorted = _data2Render.sort((a, b) => a.name.localeCompare(b.name)); + + setData2Render(_data2RenderSorted); + }, [currentPage, data, sortedData, selectedEntity, maxBarsCount, showAttributes]); return ( <> @@ -277,6 +289,7 @@ export const Table = ({ data, itemsPerPage, showBarPlot, showAttributes, selecte data={data2Render[index].data} marginPercentage={{ top: 0.1, right: 0, left: 0, bottom: 0 }} className="h-[4rem] max-h-[4rem]" + name={data2Render[index].name} /> ) : ( <BarPlot @@ -286,6 +299,7 @@ export const Table = ({ data, itemsPerPage, showBarPlot, showAttributes, selecte marginPercentage={{ top: 0.1, right: 0, left: 0, bottom: 0 }} className="h-[4rem] max-h-[4rem]" maxBarsCount={maxBarsCount} + name={data2Render[index].name} /> ) ) : ( @@ -304,11 +318,24 @@ export const Table = ({ data, itemsPerPage, showBarPlot, showAttributes, selecte {currentPage.currentData.map((item, rowIndex) => ( <tr key={rowIndex}> {dataColumns.map((col, colIndex) => ( - <td className="border-light" data-datatype={item.type[col]} key={colIndex}> + <td + className={`px-4 py-2 ${ + item.attribute[col] === undefined || + ((typeof item.attribute[col] !== 'object' || Array.isArray(item.attribute[col])) && + (item.attribute[col] as any).toString().trim() === '') || + (typeof item.attribute[col] === 'object' && + item.attribute[col] !== null && + Object.keys(item.attribute[col] as object).length === 0) + ? styles['diagonal-lines'] + : 'border-light' + }`} + data-datatype={item.type[col]} + key={colIndex} + > {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 d1447b756..a78018bb8 100644 --- a/libs/shared/lib/vis/visualizations/tablevis/components/table.module.scss +++ b/libs/shared/lib/vis/visualizations/tablevis/components/table.module.scss @@ -1,3 +1,9 @@ +.diagonal-lines { + border: 1px solid white; + background: + repeating-linear-gradient(-45deg, transparent, transparent 6px, #eaeaea 6px, #eaeaea 8px), + /* Gray diagonal lines */ linear-gradient(to bottom, transparent, transparent); /* Vertical gradient */ +} .table-container { @apply w-full relative overflow-x-auto; } diff --git a/libs/shared/lib/vis/visualizations/tablevis/tablevis.stories.tsx b/libs/shared/lib/vis/visualizations/tablevis/tablevis.stories.tsx index 9b36447bf..4a003577e 100644 --- a/libs/shared/lib/vis/visualizations/tablevis/tablevis.stories.tsx +++ b/libs/shared/lib/vis/visualizations/tablevis/tablevis.stories.tsx @@ -39,7 +39,11 @@ export const TestWithAirport = { args: { ...(await mockData.bigMockQueryResults()), schema: simpleSchemaAirportRaw, - settings: { ...TableComponent.settings, displayEntity: 'airports' }, + settings: { + ...TableComponent.settings, + displayAttributes: ['city', 'country', 'lat', 'long', 'name', 'state', 'vip'], + displayEntity: 'airports', + }, }, }; @@ -47,15 +51,22 @@ export const TestWithBig2ndChamber = { args: { ...(await mockData.big2ndChamberQueryResult()), schema: big2ndChamberSchemaRaw, - settings: { ...TableComponent.settings, displayEntity: 'kamerleden' }, + settings: { + ...TableComponent.settings, + displayAttributes: ['naam', 'anc', 'img', 'leeftijd', 'height', 'partij', 'woonplaats'], + displayEntity: 'kamerleden', + }, }, }; - export const TestWithTypesMock = { args: { ...(await mockData.typesMockQueryResults()), schema: typesMockSchemaRaw, - settings: { ...TableComponent.settings, displayEntity: 'worker' }, + settings: { + ...TableComponent.settings, + displayAttributes: ['name', 'age', 'height', 'sacked', 'birthdate', 'startingSchedule', 'commutingDuration', 'firstLogin'], + displayEntity: 'worker', + }, }, }; diff --git a/libs/shared/lib/vis/visualizations/tablevis/tablevis.tsx b/libs/shared/lib/vis/visualizations/tablevis/tablevis.tsx index f92d97beb..def795508 100644 --- a/libs/shared/lib/vis/visualizations/tablevis/tablevis.tsx +++ b/libs/shared/lib/vis/visualizations/tablevis/tablevis.tsx @@ -1,11 +1,11 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; import { Table, AugmentedNodeAttributes } from './components/Table'; -import { SchemaAttribute } from '../../../schema'; import { VisualizationPropTypes, VISComponentType, VisualizationSettingsPropTypes } from '../../common'; import { Input } from '@graphpolaris/shared/lib/components/inputs'; import { SettingsContainer } from '@graphpolaris/shared/lib/vis/components/config'; import { Button } from '@graphpolaris/shared/lib/components/buttons'; import { ArrowDropDown } from '@mui/icons-material'; +import { useSearchResultData } from '@graphpolaris/shared/lib/data-access'; export type TableProps = { id: string; @@ -28,31 +28,76 @@ const settings: TableProps = { }; export const TableVis = ({ data, schema, settings, updateSettings, graphMetadata }: VisualizationPropTypes<TableProps>) => { + const searchResults = useSearchResultData(); const ref = useRef<HTMLDivElement>(null); - useEffect(() => { - if (!graphMetadata.nodes.labels.includes(settings.displayEntity)) { - updateSettings({ displayEntity: graphMetadata.nodes.labels[0] }); + if (graphMetadata != undefined) { + if (!graphMetadata.nodes.labels.includes(settings.displayEntity)) { + updateSettings({ + displayEntity: graphMetadata.nodes.labels[0], + displayAttributes: Object.keys(graphMetadata.nodes.types[graphMetadata.nodes.labels[0]].attributes), + }); + } } - }, [graphMetadata.nodes.labels, data, settings]); + }, [graphMetadata, data, settings]); + + const attributesArray = useMemo<AugmentedNodeAttributes[]>(() => { + //const similiarityThreshold = 0.9; + let displayAttributesSorted: string[]; + + displayAttributesSorted = [...settings.displayAttributes].sort((a, b) => a.localeCompare(b)); + + const dataNodes = (searchResults?.nodes?.length ?? 0) === 0 ? data.nodes : searchResults.nodes; - const attributesArray = useMemo<AugmentedNodeAttributes[]>( - () => - data.nodes - .filter((node) => node.label === settings.displayEntity) + return ( + dataNodes + .filter((node) => { + // some dataset do not have label field + let labelNode = ''; + if (node.label !== undefined) { + labelNode = node.label; + } else { + const idParts = node._id.split('/'); + labelNode = idParts[0]; + } + return labelNode === settings.displayEntity; + }) + ///.filter((obj) => obj.similarity === undefined || obj.similarity >= similiarityThreshold) .map((node) => { - const types: SchemaAttribute[] = - schema.nodes.find((n) => n.key === node.label)?.attributes?.attributes ?? - schema.edges.find((r) => r.key === node.label)?.attributes?.attributes ?? - []; - - return { - attribute: node.attributes, - type: Object.fromEntries(types.map((t) => [t.name, t.type])), - }; - }), - [data.nodes, settings.displayEntity], - ); + // get attributes filtered and sorted + const filteredAttributes = Object.fromEntries( + Object.entries(node.attributes) + .filter(([attr]) => settings.displayAttributes.includes(attr)) + .sort(([attrA], [attrB]) => settings.displayAttributes.indexOf(attrA) - settings.displayAttributes.indexOf(attrB)), + ); + + // doubled types structure to handle discrepancies in schema object in sb and dev env. + + let types = + schema.nodes.find((n: any) => { + let labelNode = node.label; + return labelNode === n.key; + })?.attributes?.attributes ?? + schema.nodes.find((n: any) => { + let labelNode = node.label; + + return labelNode === n.name; + })?.attributes; + + if (types) { + return { + attribute: filteredAttributes, + type: Object.fromEntries(types.map((t: any) => [t.name, t.type])), + }; + } else { + return { + attribute: filteredAttributes, + type: {}, + }; + } + }) + ); + }, [data.nodes, settings.displayEntity, settings.displayAttributes, searchResults]); return ( <div className="h-full w-full" ref={ref}> @@ -81,11 +126,12 @@ const TableSettings = ({ settings, graphMetadata, updateSettings }: Visualizatio const toggleCollapseAttr = () => { setAreCollapsedAttr(!areCollapsedAttr); }; + const selectedNodeAttributes = useMemo(() => { if (settings.displayEntity) { const nodeType = graphMetadata.nodes.types[settings.displayEntity]; if (nodeType && nodeType.attributes) { - return Object.keys(nodeType.attributes); + return Object.keys(nodeType.attributes).sort((a, b) => a.localeCompare(b)); } } return []; @@ -95,7 +141,7 @@ const TableSettings = ({ settings, graphMetadata, updateSettings }: Visualizatio if (graphMetadata && graphMetadata.nodes && graphMetadata.nodes.labels.length > 0) { updateSettings({ displayAttributes: selectedNodeAttributes }); } - }, [selectedNodeAttributes]); + }, [selectedNodeAttributes, graphMetadata]); return ( <SettingsContainer> -- GitLab