Skip to content
This repository has been archived by the owner on Apr 16, 2024. It is now read-only.

Commit

Permalink
Staging (#161)
Browse files Browse the repository at this point in the history
* edit signin page

* add error message on login error

* add node click to expend
  • Loading branch information
gkorland authored Nov 22, 2023
1 parent 0b26c1a commit 911a2c3
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 58 deletions.
2 changes: 1 addition & 1 deletion app/api/graph/[graph]/[node]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export async function GET(request: NextRequest, { params }: { params: { graph: s
try {
const client = await getClient(user)
const graph = new Graph(client, params.graph);
let result = await graph.query("Match (s)-[r]->(t) where ID(s) = $id return r,t", { params: { id: parseInt(params.node) } })
let result = await graph.query("Match (s)-[r]-(t) where ID(s) = $id return r,t", { params: { id: parseInt(params.node) } })
return NextResponse.json({ result: result }, { status: 200 })
} catch (err: any) {
return NextResponse.json({ message: err.message }, { status: 400 })
Expand Down
128 changes: 96 additions & 32 deletions app/components/DirectedGraph.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import ReactEcharts, { EChartsOption } from "echarts-for-react";
import React, { useRef } from 'react';
import ReactEcharts, { EChartsInstance } from "echarts-for-react";

export interface Category {
name: string,
Expand Down Expand Up @@ -41,17 +41,23 @@ function getOption(nodes: GraphData[], edges: GraphLink[], categories: Category[
},
series: [
{
nodes,
edges,
categories,

type: "graph",
layout: "force",
force: {
edgeLength: 70,
repulsion: 150,
gravity: 0.1
},
draggable: true,
label: {
position: 'center',
show: true,
formatter: '{b}',
formatter: '{b}',
},
nodes: nodes,
edges: edges,
categories: categories,
emphasis: {
focus: 'adjacency',
label: {
Expand All @@ -61,41 +67,99 @@ function getOption(nodes: GraphData[], edges: GraphLink[], categories: Category[
},
roam: true,
lineStyle: {
color: 'source',
width: 3.0,
curveness: 0.2,
},
dagreLayout: {
rankdir: 'LR', // Left to right layout
nodesepFunc: () => 1, // Node separation function
ranksepFunc: () => 1, // Rank separation function
curveness: 0.1,
opacity: 0.7
},
symbolSize: 30,
},
],
};
};

export function DirectedGraph(
props: {
nodes: GraphData[],
edges: GraphLink[],
categories: Category[],
onChartClick: (id: number) => Promise<[Category[], GraphData[], GraphLink[]]>
nodes: Map<number, GraphData>,
edges: Set<GraphLink>,
categories: Map<String, Category>,
onChartClick: (id: number) => Promise<[Map<String, Category>, Map<number, GraphData>, Set<GraphLink>]>
}) {
const echartRef = useRef<EChartsInstance | null>(null)

const nodes = Array.from(props.nodes.values())
const edges = Array.from(props.edges.values())
const categories = Array.from(props.categories.values())

let options = getOption(nodes, edges, categories)

let onEvents: { click: (params: any) => void } = {
click: async (params: any) => {
let [newCategories, newNodes, newEdges] = await props.onChartClick(parseInt(params.data.id))

let newCategoriesArray = new Array<Category>(newCategories.size)
newCategories.forEach((category) => {
newCategoriesArray[category.index] = category
})

newNodes.forEach((node, id) => {
if (!props.nodes.get(id)) {

// Check if category already exists in categories otherwise add it
let newCategory = newCategoriesArray[node.category]
let category = props.categories.get(newCategory.name)

// let [nodes, setNodes] = React.useState<GraphData[]>(props.data)
// let [edges, setEdges] = React.useState<GraphLink[]>(props.links)
// let [categories, setCategories] = React.useState<Category[]>(props.categories)

// let onEvents: { echartRef: any, click: (params: any) => void } = {
// echartRef: null,
// click: async (params: any) => {
// let [newCategories, newNodes, newEdges] = await props.onChartClick(parseInt(params.data.id))

// setNodes([...nodes, ...newNodes]);
// setEdges([...edges, ...newEdges]);
// }
// }

// return (<ReactEcharts ref={(e) => { onEvents.echartRef = e }} option={getOption(nodes, edges, categories)} onEvents={onEvents} />)
return (<ReactEcharts className="border" option={getOption(props.nodes, props.edges, props.categories)} />)
if(category) {
node.category = category.index
} else {
newCategory.index = categories.length
node.category = newCategory.index
props.categories.set(newCategory.name, newCategory)
categories.push(newCategory)
}

// Add the node to the nodes map and array
props.nodes.set(parseInt(node.id), node)
nodes.push(node)
}
})

newEdges.forEach((edge) => {
if (!props.edges.has(edge)) {
props.edges.add(edge)
edges.push(edge)
}
})

echartRef.current?.setOption({
legend: [
{
data: categories.map(function (c) {
return c.name;
})
}
],
series: [
{
nodes,
edges,
categories,
}

]
})
}
}

return (
<ReactEcharts
style={{ height: "50vh"}}
className="border"
option={options}
onEvents={onEvents}
onChartReady={
(e) => { echartRef.current = e }
}
/>
)
}
51 changes: 26 additions & 25 deletions app/sandbox/CypherInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ interface GraphResult {
interface ExtractedData {
data: any[][],
columns: string[],
categories: Category[],
nodes: GraphData[],
edges: GraphLink[]
categories: Map<String, Category>,
nodes: Map<number, GraphData>,
edges: Set<GraphLink>,
}


Expand All @@ -64,12 +64,11 @@ function extractData(results: GraphResult | null) : ExtractedData {
data = results.data
}

let nodesMap = new Map<number, GraphData>();
let categoriesMap = new Map<String, Category>();
categoriesMap.set("default", { name: "default", index: 0})
let categories: Category[] = [{ name: "default", index: 0}]
let nodes = new Map<number, GraphData>()
let categories = new Map<String, Category>()
categories.set("default", { name: "default", index: 0})

let edges: GraphLink[] = []
let edges = new Set<GraphLink>()

data.forEach((row: any[]) => {
Object.values(row).forEach((cell: any) => {
Expand All @@ -78,42 +77,40 @@ function extractData(results: GraphResult | null) : ExtractedData {

let sourceId = cell.sourceId.toString();
let destinationId = cell.destinationId.toString()
edges.push({ source: sourceId, target: destinationId })
edges.add({ source: sourceId, target: destinationId })

// creates a fakeS node for the source and target
let source = nodesMap.get(cell.sourceId)
let source = nodes.get(cell.sourceId)
if(!source) {
source = { id: cell.sourceId.toString(), name: cell.sourceId.toString(), value: "", category: 0 }
nodesMap.set(cell.sourceId, source)
nodes.set(cell.sourceId, source)
}

let destination = nodesMap.get(cell.destinationId)
let destination = nodes.get(cell.destinationId)
if(!destination) {
destination = { id: cell.destinationId.toString(), name: cell.destinationId.toString(), value: "", category: 0 }
nodesMap.set(cell.destinationId, destination)
nodes.set(cell.destinationId, destination)
}
} else if (cell.labels) {

// check if category already exists in categories
let category = categoriesMap.get(cell.labels[0])
let category = categories.get(cell.labels[0])
if (!category) {
category = { name: cell.labels[0], index: categories.length }
categoriesMap.set(category.name, category)
categories.push(category)
category = { name: cell.labels[0], index: categories.size }
categories.set(category.name, category)
}

// check if node already exists in nodes or fake node was created
let node = nodesMap.get(cell.id)
let node = nodes.get(cell.id)
if (!node || node.value === "") {
node = { id: cell.id.toString(), name: cell.id.toString(), value: JSON.stringify(cell), category: category.index }
nodesMap.set(cell.id, node)
nodes.set(cell.id, node)
}
}
}
})
})

let nodes: GraphData[] = Array.from(nodesMap.values())
return { data, columns, categories, nodes, edges}
}

Expand Down Expand Up @@ -185,7 +182,7 @@ export function CypherInput(props: { onSubmit: (graph: string, query: string) =>
}

// A function that handles the click event of the Graph
async function handleGraphClick(id: number) : Promise<[Category[], GraphData[], GraphLink[]]>{
async function handleGraphClick(id: number) : Promise<[Map<String,Category>, Map<number, GraphData>, Set<GraphLink>]>{

let results = await props.onGraphClick(selectedGraph, id)
let extracted = extractData(results)
Expand All @@ -195,7 +192,7 @@ export function CypherInput(props: { onSubmit: (graph: string, query: string) =>
let extracted = extractData(results)

// If the result holds data to present in the graph tab, set it as the default tab
const defaultTab = (extracted.nodes.length > 0 || extracted.edges.length > 0) ? "graph" : "table"
const defaultTab = (extracted.nodes.size > 0 || extracted.edges.size > 0) ? "graph" : "table"

return (
<div className="flex flex-col">
Expand All @@ -211,13 +208,17 @@ export function CypherInput(props: { onSubmit: (graph: string, query: string) =>
{!valid && <p className="text-red-600">Invalid Cypher query. Please check the syntax.</p>}
{extracted.data.length > 0 && (
<>
<Tabs defaultValue={defaultTab} className="w-full grow py-2">
<Tabs defaultValue={defaultTab} className="w-full grow py-2 min-h-900">
<TabsList>
<TabsTrigger value="table">Data</TabsTrigger>
<TabsTrigger value="graph">Graph</TabsTrigger>
</TabsList>
<TabsContent value="table"><DataTable rows={extracted.data} columnNames={extracted.columns} /></TabsContent>
<TabsContent value="graph"><DirectedGraph nodes={extracted.nodes} edges={extracted.edges} categories={extracted.categories} onChartClick={handleGraphClick} /></TabsContent>
<TabsContent value="table">
<DataTable rows={extracted.data} columnNames={extracted.columns} />
</TabsContent>
<TabsContent value="graph">
<DirectedGraph nodes={extracted.nodes} edges={extracted.edges} categories={extracted.categories} onChartClick={handleGraphClick} />
</TabsContent>
</Tabs>
</>
)}
Expand Down

0 comments on commit 911a2c3

Please sign in to comment.