diff --git a/libs/shared/lib/components/charts/barplot/index.tsx b/libs/shared/lib/components/charts/barplot/index.tsx index ba038b94ef3f2dc75eeda9fc451bbf37d8e68e26..53d6344dea4656b343df85d9735cae03be03b043 100644 --- a/libs/shared/lib/components/charts/barplot/index.tsx +++ b/libs/shared/lib/components/charts/barplot/index.tsx @@ -1,6 +1,7 @@ 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'; +import { SelectionStateI } from '@graphpolaris/shared/lib/data-access/store/interactionSlice'; export type BarPlotProps = { data: { category: string; count: number }[]; @@ -17,9 +18,10 @@ export type BarPlotProps = { strokeWidth?: number; axis?: boolean; name: string; + selection?: SelectionStateI; }; -export const BarPlot = ({ typeBarPlot, numBins, data, marginPercentage, className, maxBarsCount, axis, name }: BarPlotProps) => { +export const BarPlot = ({ typeBarPlot, numBins, data, marginPercentage, className, maxBarsCount, axis, name, selection }: BarPlotProps) => { const svgRef = useRef<SVGSVGElement | null>(null); const groupMarginRef = useRef<SVGGElement | null>(null); @@ -180,6 +182,13 @@ export const BarPlot = ({ typeBarPlot, numBins, data, marginPercentage, classNam </TooltipContent> </Tooltip> ))} + { selection != null && + <line + x1={selection} + x2={selection} + y1={0} + y2={heightSVGwithinMargin} /> + } </g> </svg> </div> diff --git a/libs/shared/lib/inspector/InspectorPanel.tsx b/libs/shared/lib/inspector/InspectorPanel.tsx index 5aebf62b2b452cac7d130d7ceea233aa7e431169..84257549e272407a47c7a21b345685e3311ee656 100644 --- a/libs/shared/lib/inspector/InspectorPanel.tsx +++ b/libs/shared/lib/inspector/InspectorPanel.tsx @@ -18,7 +18,7 @@ export function InspectorPanel(props: { children?: React.ReactNode }) { const { activeVisualizationIndex } = useVisualization(); const inspector = useMemo(() => { - //if (selection) return <SelectionConfig />; + if (selection) return <SelectionConfig />; // if (!focus) return <ConnectionInspector />; // if (activeVisualizationIndex !== -1) return <ConnectionInspector />; return <VisualizationSettings />; diff --git a/libs/shared/lib/vis/components/config/SelectionConfig.tsx b/libs/shared/lib/vis/components/config/SelectionConfig.tsx index 2e5d9d687d770e44d5b09e9452b1d7a9a616affe..c51b1805b7aa6b5677eda190ac15f0014c91ade4 100644 --- a/libs/shared/lib/vis/components/config/SelectionConfig.tsx +++ b/libs/shared/lib/vis/components/config/SelectionConfig.tsx @@ -2,16 +2,65 @@ import React from 'react'; import { SelectionStateI, unSelect } from '@graphpolaris/shared/lib/data-access/store/interactionSlice'; import { useDispatch } from 'react-redux'; import { Button, EntityPill, useSelection } from '../../..'; -import { SettingsHeader } from './components'; +import { BarPlot } from '@graphpolaris/shared/lib/components/charts/barplot'; +import { Node, Edge, useGraphQueryResult } from '@graphpolaris/shared/lib/data-access'; +import * as d3 from 'd3'; export const SelectionConfig = () => { const selection = useSelection(); const dispatch = useDispatch(); + const result = useGraphQueryResult(); + if (!selection) return null; + function getData(item: Node | Edge, attribute: string) { + const data = result.nodes + .filter(x => x.label == (item.attributes['labels'] as string[])[0]) + .map(x => x.attributes[attribute]); + + return data.map(x => ({ + category: "placeholder", + count: parseFloat(x) + })) + + // TODO: FIXME + + + // const bins = d3.bin()(data); + + // return bins.map(bin => { + // return { + // category: bin.x0, + // count: bin.length, + // } + // }); + } + + function isNumber(item: Node | Edge, attribute: string) { + return !Number.isNaN(parseFloat(item.attributes[attribute] as string)); + } + + function showPlot(item: Node | Edge, attribute: string) { + + if (isNumber(item, attribute)) { + return true; + } + + const data = result.nodes + .filter(x => x.label == (item.attributes['labels'] as string[])[0]) + .map(x => x.attributes[attribute]); + + const countUnique = (new Set(data)).size; + if (countUnique < 20) { + return true; + } + + return false; + } + return ( - <div className="border-b py-2"> + <div className="border-b py-2 overflow-auto"> <div className="flex justify-between items-center px-4 py-2"> <span className="text-xs font-bold">Selection</span> <Button @@ -34,13 +83,41 @@ export const SelectionConfig = () => { <span className="text-xs font-normal">Label</span> <EntityPill title={item.attributes['labels'] as string}></EntityPill> </div> + <hr class="my-2" /> + <span className="text-xs font-bold px-4">Attributes</span> {Object.entries(item.attributes).map(([key, value]) => { if (key === 'labels' || key === '_id') return null; return ( - <div key={index + key} className="flex justify-between items-center px-4 py-1 gap-1"> - <span className="text-xs font-normal break-all max-w-[6rem]">{key}</span> - <span className="text-xs break-all">{value as string}</span> - </div> + <> + { showPlot(item, key) && <div className="border-b"> + { isNumber(item, key) ? <BarPlot + typeBarPlot="numerical" + numBins={20} + data={getData(item, key)} + marginPercentage={{ top: 0.1, right: 0, left: 0, bottom: 0 }} + className="h-[4rem] max-h-[4rem]" + name={key} + selection={selection} + /> + : + <BarPlot + typeBarPlot="categorical" + numBins={20} + data={getData(item, key)} + marginPercentage={{ top: 0.1, right: 0, left: 0, bottom: 0 }} + className="h-[4rem] max-h-[4rem]" + maxBarsCount={100} + name={key} + selection={selection} + /> + } + </div> + } + <div key={index + key} className="flex justify-between items-center px-4 py-1 gap-1 border-b border-slate-400"> + <span className="text-xs font-normal break-all max-w-[6rem]">{key}</span> + <span className="text-xs break-all">{value as string}</span> + </div> + </> ); })} </React.Fragment> diff --git a/libs/shared/lib/vis/visualizations/nodelinkvis/nodelinkvis.tsx b/libs/shared/lib/vis/visualizations/nodelinkvis/nodelinkvis.tsx index 911eb5acba79eea63bacfc4c72b52ea619df1d5b..16cb7e0e7bd48cd407b85e294ab3f759adc5a1bf 100644 --- a/libs/shared/lib/vis/visualizations/nodelinkvis/nodelinkvis.tsx +++ b/libs/shared/lib/vis/visualizations/nodelinkvis/nodelinkvis.tsx @@ -171,14 +171,14 @@ const NodelinkSettings = ({ settings, graphMetadata, updateSettings }: Visualiza <Input type="boolean" label="Common shape?" - value={settings.shapes.similar} + value={settings.shapes?.similar} onChange={(val) => updateSettings({ shapes: { ...settings.shapes, similar: val } })} /> - {settings.shapes.similar ? ( + {settings.shapes?.similar ? ( <Input type="dropdown" label="Shape" - value={settings.shapes.shape} + value={settings.shapes?.shape} options={[{ circle: 'Circle' }, { rectangle: 'Square' }]} onChange={(val) => updateSettings({ shapes: { ...settings.shapes, shape: val as any } })} />