diff --git a/.husky/pre-commit b/.husky/pre-commit
index af0cff7ed76ca87b56598342144e51ff6de92c55..aaefaffe8a6200550ca3c132e696ab9691e30eb2 100755
--- a/.husky/pre-commit
+++ b/.husky/pre-commit
@@ -1,4 +1,4 @@
 #!/usr/bin/env sh
 . "$(dirname -- "$0")/_/husky.sh"
 
-pnpm test
+pnpm push
diff --git a/.husky/pre-commit-lint b/.husky/pre-commit-lint
deleted file mode 100755
index 58993aaeefd1e2be0b83d453941fadbd7b74266d..0000000000000000000000000000000000000000
--- a/.husky/pre-commit-lint
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/usr/bin/env sh
-. "$(dirname -- "$0")/_/husky.sh"
-
-pnpm lint
diff --git a/Makefile b/Makefile
index 6a5fee121cc4ab224d8f47fff4fd84cb9b7ffe1b..9d40cf0287500d9625478489852bcead9df24cff 100644
--- a/Makefile
+++ b/Makefile
@@ -17,9 +17,7 @@ run:
 brun: build run
 
 push:
-	@pnpm lint
-	@pnpm test
-	@pnpm build
+	@pnpm push
 
 clean:
 	rm -rf pnpm-lock.yaml
diff --git a/apps/web/src/app/panels/Visualization.tsx b/apps/web/src/app/panels/Visualization.tsx
index 13776d7554427354b31f99abe12fcda57bf24c63..295a74012c949a19279e9a76ee6afdebb9e22569 100644
--- a/apps/web/src/app/panels/Visualization.tsx
+++ b/apps/web/src/app/panels/Visualization.tsx
@@ -1,5 +1,5 @@
 import React, { useMemo } from 'react';
-import { RawJSONVis, NodeLinkVis, PaohVis } from '@graphpolaris/shared/lib/vis';
+import { RawJSONVis, NodeLinkVis, PaohVis, TableVis } from '@graphpolaris/shared/lib/vis';
 import { useGraphQueryResult, useQuerybuilderGraph, useVisualizationState } from '@graphpolaris/shared/lib/data-access';
 import { Visualizations } from '@graphpolaris/shared/lib/data-access/store/visualizationSlice';
 import { LoadingSpinner } from '@graphpolaris/shared/lib/components/LoadingSpinner';
@@ -29,10 +29,16 @@ export const VisualizationPanel = () => {
             <PaohVis rowHeight={30} hyperedgeColumnWidth={30} gapBetweenRanges={3} />
           </div>
         );
+      case Visualizations.Table:
+        return (
+          <div id={Visualizations.Table} className="tabContent w-full h-full">
+            <TableVis showBarplot={true} />
+          </div>
+        );
       default:
         return null;
     }
-  }, [graphQueryResult]);
+  }, [graphQueryResult, vis.activeVisualization]);
 
   return (
     <div className="vis-panel h-full w-full overflow-y-auto" style={graphQueryResult.nodes.length === 0 ? { overflow: 'hidden' } : {}}>
diff --git a/apps/web/src/components/navbar/navbar.tsx b/apps/web/src/components/navbar/navbar.tsx
index ff78645393585300a4c19323b59c36366a898c95..44a609b544fb4fff56515416f5951c14c4690669 100644
--- a/apps/web/src/components/navbar/navbar.tsx
+++ b/apps/web/src/components/navbar/navbar.tsx
@@ -85,6 +85,9 @@ export const Navbar = (props: NavbarComponentProps) => {
             Vis
           </label>
           <ul tabIndex={0} className="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-52">
+            <li onClick={() => dispatch(setActiveVisualization(Visualizations.Table))}>
+              <a>Table</a>
+            </li>
             <li onClick={() => dispatch(setActiveVisualization(Visualizations.NodeLink))}>
               <a>Node Link</a>
             </li>
diff --git a/libs/shared/lib/data-access/store/graphQueryResultSlice.ts b/libs/shared/lib/data-access/store/graphQueryResultSlice.ts
index 773e5891de6af6555e9ca92eba3b4f3627f6940c..be7e1efb83efa89eda7c1c28f0dc032475782d52 100755
--- a/libs/shared/lib/data-access/store/graphQueryResultSlice.ts
+++ b/libs/shared/lib/data-access/store/graphQueryResultSlice.ts
@@ -9,17 +9,19 @@ export interface GraphQueryResultFromBackendPayload {
   };
 }
 
+export type NodeAttributes = { [key: string]: unknown }
+
 export interface GraphQueryResultFromBackend {
   nodes: {
     id: string;
     label?: string;
-    attributes: { [key: string]: unknown };
+    attributes: NodeAttributes;
   }[];
 
   edges: {
     id: string;
     label?: string;
-    attributes: { [key: string]: unknown };
+    attributes: NodeAttributes;
     from: string;
     to: string;
   }[];
@@ -31,12 +33,12 @@ export interface GraphQueryResultFromBackend {
 export interface Node {
   id: string;
   label: string;
-  attributes: { [key: string]: unknown };
+  attributes: NodeAttributes;
   mldata?: any; // FIXME
   /* type: string[]; */
 }
 export interface Edge {
-  attributes: { [key: string]: unknown };
+  attributes: NodeAttributes;
   from: string;
   to: string;
   id: string;
diff --git a/libs/shared/lib/data-access/store/visualizationSlice.ts b/libs/shared/lib/data-access/store/visualizationSlice.ts
index 367ceccea6e4102c84127318297a463339e0abcc..b7415b4573a5bd31c83740726c0bda4bf7061d21 100644
--- a/libs/shared/lib/data-access/store/visualizationSlice.ts
+++ b/libs/shared/lib/data-access/store/visualizationSlice.ts
@@ -5,6 +5,7 @@ export enum Visualizations {
   NodeLink = 'NodeLink',
   Paohvis = 'Paohvis',
   RawJSON = 'RawJSON',
+  Table = 'Table',
 }
 
 type VisState = {
diff --git a/libs/shared/lib/querybuilder/panel/stories/querybuilder-simple-disconnected.stories.tsx b/libs/shared/lib/querybuilder/panel/stories/querybuilder-simple-disconnected.stories.tsx
deleted file mode 100644
index df2bc62e8161339208a97bb73c786988add619fb..0000000000000000000000000000000000000000
--- a/libs/shared/lib/querybuilder/panel/stories/querybuilder-simple-disconnected.stories.tsx
+++ /dev/null
@@ -1,105 +0,0 @@
-import React from 'react';
-import { querybuilderSlice, setQuerybuilderNodes, setSchema, store } from '@graphpolaris/shared/lib/data-access/store';
-
-import { configureStore } from '@reduxjs/toolkit';
-import { Meta, StoryObj } from '@storybook/react';
-import { Provider } from 'react-redux';
-import QueryBuilderInner from '../querybuilder';
-import { Handles, NodeAttribute, QueryElementTypes, QueryMultiGraphology, toHandleId } from '../../model';
-import { SchemaUtils } from '../../../schema/schema-utils';
-import { ReactFlowProvider } from 'reactflow';
-
-const Component: Meta<typeof QueryBuilderInner> = {
-  component: QueryBuilderInner,
-  title: 'QueryBuilder/Panel/SimpleDisconnected',
-  decorators: [(story) => <div>{story()}</div>],
-};
-
-export const SimpleDisconnected: StoryObj = {
-  args: {
-    nodes: [
-      {
-        name: 'entity',
-        attributes: [
-          { name: 'city', type: 'string' },
-          { name: 'vip', type: 'bool' },
-          { name: 'state', type: 'string' },
-        ],
-      },
-    ],
-    edges: [
-      {
-        name: 'entity:entity',
-        from: 'entity',
-        to: 'entity',
-        collection: 'entity2entity',
-        attributes: [
-          { name: 'arrivalTime', type: 'int' },
-          { name: 'departureTime', type: 'int' },
-        ],
-      },
-    ],
-  },
-  decorators: [
-    (story: any, { args }: any) => {
-      console.log(args);
-
-      const graph = new QueryMultiGraphology();
-      const schema = SchemaUtils.schemaBackend2Graphology({
-        nodes: args.nodes,
-        edges: args.edges,
-      });
-
-      store.dispatch(setSchema(schema.export()));
-
-      const entity1 = graph.addPill2Graphology(
-        {
-          id: '0',
-          type: QueryElementTypes.Entity,
-          x: 100,
-          y: 100,
-          name: 'Airport 1',
-        },
-        schema.getNodeAttribute('entity', 'attributes')
-      );
-      const entity2 = graph.addPill2Graphology(
-        {
-          id: '10',
-          type: QueryElementTypes.Entity,
-          x: 200,
-          y: 200,
-          name: 'Airport 2',
-        },
-        schema.getNodeAttribute('entity', 'attributes')
-      );
-
-      // graph.addNode('0', { type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Entity Pill' });
-      const relation1 = graph.addPill2Graphology({
-        id: '1',
-        type: QueryElementTypes.Relation,
-        x: 140,
-        y: 140,
-        name: 'Flight between airports',
-        collection: 'Relation Pill',
-        depth: { min: 0, max: 1 },
-        attributes: schema.getEdgeAttribute('entity:entity_entityentity', 'attributes'),
-      });
-      store.dispatch(setQuerybuilderNodes(graph.export()));
-
-      return (
-        <Provider store={store}>
-          <div
-            style={{
-              width: '100%',
-              height: '95vh',
-            }}
-          >
-            <ReactFlowProvider>{story()}</ReactFlowProvider>
-          </div>
-        </Provider>
-      );
-    },
-  ],
-};
-
-export default Component;
diff --git a/libs/shared/lib/querybuilder/panel/stories/querybuilder-simple.stories.tsx b/libs/shared/lib/querybuilder/panel/stories/querybuilder-simple.stories.tsx
index 68c0849ba9d04824ba1cc1781dc0b40bdc16cd42..ed0939b844ced9ac89ebef13a56f4f4ce66e3681 100644
--- a/libs/shared/lib/querybuilder/panel/stories/querybuilder-simple.stories.tsx
+++ b/libs/shared/lib/querybuilder/panel/stories/querybuilder-simple.stories.tsx
@@ -81,16 +81,18 @@ export const Simple = {
     );
 
     // graph.addNode('0', { type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Entity Pill' });
-    const relation1 = graph.addPill2Graphology({
-      id: '1',
-      type: QueryElementTypes.Relation,
-      x: 140,
-      y: 140,
-      name: 'Flight between airports',
-      collection: 'Relation Pill',
-      depth: { min: 0, max: 1 },
-      attributes: schema.getEdgeAttribute('entity:entity_entityentity', 'attributes'),
-    });
+    const relation1 = graph.addPill2Graphology(
+      {
+        id: '1',
+        type: QueryElementTypes.Relation,
+        x: 140,
+        y: 140,
+        name: 'Flight between airports',
+        collection: 'Relation Pill',
+        depth: { min: 0, max: 1 },
+      },
+      schema.getEdgeAttribute('entity:entity_entityentity', 'attributes')
+    );
     // addPill2Graphology(
     //   '2',
     //   {
diff --git a/libs/shared/lib/schema/model/graphology.ts b/libs/shared/lib/schema/model/graphology.ts
index 773ff2b986caa71937fd3e90fc54da1af53e4f48..5e107237ce4430ccf26992ebb5381b512889054e 100644
--- a/libs/shared/lib/schema/model/graphology.ts
+++ b/libs/shared/lib/schema/model/graphology.ts
@@ -1,6 +1,6 @@
 import { MultiGraph } from 'graphology';
 import { Attributes as GAttributes, NodeEntry, EdgeEntry, SerializedGraph } from 'graphology-types';
-import { SchemaAttribute, SchemaNode } from './FromBackend';
+import { SchemaAttribute, SchemaNode, SchemaEdge } from './FromBackend';
 
 /** Attribute type, consist of a name */
 export type SchemaGraphologyNode = GAttributes & SchemaNode;
diff --git a/libs/shared/lib/schema/schema-utils/schema-usecases.ts b/libs/shared/lib/schema/schema-utils/schema-usecases.ts
index fcd643a90a1e129b2f41ed86b2c7357954610d57..abbe835f06f802b9df3ac0d21b6e9cb155052902 100644
--- a/libs/shared/lib/schema/schema-utils/schema-usecases.ts
+++ b/libs/shared/lib/schema/schema-utils/schema-usecases.ts
@@ -22,14 +22,9 @@ export function schemaExpandRelation(graph: SchemaGraphology): SchemaGraphology
     const newID = 'RelationNode:' + edge;
     // console.log('making relationnode', edge, attributes, source, target, newID);
     newGraph.addNode(newID, {
+      ...attributes,
       name: edge,
       label: edge,
-      // data: {
-      //   label: edge,
-      //   name: edge,
-      // attributes: attributes
-      // },
-      ...attributes,
       attributes: [],
       x: 0,
       y: 0,
diff --git a/libs/shared/lib/vis/index.ts b/libs/shared/lib/vis/index.ts
index 1d8457e760f67b49363019e6ab01dc5f6b7ce6a1..29b87b7d31ae86c1be6a6aa160248af8e88fd5e1 100644
--- a/libs/shared/lib/vis/index.ts
+++ b/libs/shared/lib/vis/index.ts
@@ -2,4 +2,5 @@ export * from './rawjsonvis';
 export * from './nodelink/nodelinkvis';
 export * from './paohvis/paohvis';
 export * from './semanticsubstrates/semanticsubstrates';
+export * from './table_vis/tableVis';
 // export * from './geovis/NodeLinkMap';
diff --git a/libs/shared/lib/vis/simple_table_pagination/components/Pagination.tsx b/libs/shared/lib/vis/simple_table_pagination/components/Pagination.tsx
deleted file mode 100644
index 796461dfcffc4a98072d746a14b814ff1ba17e7d..0000000000000000000000000000000000000000
--- a/libs/shared/lib/vis/simple_table_pagination/components/Pagination.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import React , { useRef } from 'react';
-import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos';
-import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
-
-interface PaginationProps {
-  currentPage: number;
-  totalPages: number;
-  onPageChange: (page: number) => void;
-  itemsPerPageInput: number;
-  numItemsArrayReal: number;
-  totalItems : number;
-}
-
-const Pagination: React.FC<PaginationProps> = ({ currentPage, 
-                                                  totalPages, 
-                                                onPageChange, 
-                                           itemsPerPageInput, 
-                                           numItemsArrayReal,
-                                                  totalItems  }) => {
-  
-  const pageNumbers = Array.from({ length: totalPages }, (_, index) => index + 1);
-
-  const firstItem = (currentPage - 1) * itemsPerPageInput + 1;
-  const lastItem = Math.min(currentPage * itemsPerPageInput, totalPages); 
-
-  const goToPreviousPage = () => {
-    if (currentPage > 1) {
-      onPageChange(currentPage - 1);
-    }
-  };
-
-  const goToNextPage = () => {
-    if (currentPage < totalPages) {
-      onPageChange(currentPage + 1);
-    }
-  };
-  
-  return (
-    <div className="table-pagination">
-      <span className="inline-block m-2 max-w-32 min-w-32">
-        {`${firstItem}-${numItemsArrayReal} of ${totalItems}`}
-      </span>
-      <button className = "m-1 border-4 border-solid border-neutral p-2 w-11 text-primary" onClick={goToPreviousPage} disabled={currentPage === 1}>
-        <ArrowBackIosIcon />
-      </button>
-      <button className = "m-1 border-4 border-solid border-neutral p-2 w-11 text-primary" onClick={goToNextPage} disabled={currentPage === totalPages}>
-        <ArrowForwardIosIcon />
-      </button>
-    </div>
-  );
-};
-
-export default Pagination;
\ No newline at end of file
diff --git a/libs/shared/lib/vis/simple_table_pagination/components/Table.tsx b/libs/shared/lib/vis/simple_table_pagination/components/Table.tsx
deleted file mode 100644
index ad6b7a0ed3e036ba4218861b30e0405aa9d3875f..0000000000000000000000000000000000000000
--- a/libs/shared/lib/vis/simple_table_pagination/components/Table.tsx
+++ /dev/null
@@ -1,121 +0,0 @@
-import React, { useState, useEffect } from 'react';
-import Pagination from './Pagination';
-
-interface TableProps {
-  data: any[];
-  itemsPerPage: number;
-}
-
-const Table: React.FC<TableProps> = ({ data, itemsPerPage }) => {
-
-  const originalData = [...data];
-
-  const [sortedData, setSortedData] = useState<any[]>(data);
-  const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc');
-  const [sortColumn, setSortColumn] = useState<string | null>(null);
-  const [currentPage, setCurrentPage] = useState<number>(1);
-
-  const keys = Object.keys(data[0]);
-  const totalPages = Math.ceil(sortedData.length / itemsPerPage);
-
-  useEffect(() => {
-    if (sortColumn !== null) {
-      const sorted = [...data].sort((a, b) => {
-        if (sortOrder === 'asc') {
-          return a[sortColumn] < b[sortColumn] ? -1 : 1;
-        } else {
-          return a[sortColumn] > b[sortColumn] ? -1 : 1;
-        }
-      });
-      setSortedData(sorted);
-    }
-  }, [sortOrder, data, sortColumn]);
-
-  useEffect(() => {
-    setCurrentPage(1); // Reset to the first page when sorting or itemsPerPage changes
-  }, [sortColumn, sortOrder, itemsPerPage]);
-
-  const onPageChange = (page: number) => {
-    setCurrentPage(page);
-  };
-
-  const toggleSort = (column: string) => {
-    if (sortColumn === column) {
-      if (sortOrder === 'asc') {
-        setSortOrder('desc');
-        setSortedData([...sortedData].reverse()); // Reverse the sorted data
-      } else if (sortOrder === 'desc') {
-        setSortColumn(null);
-        setSortOrder('asc');
-        setSortedData(originalData); // Reset to the original order
-      }
-    } else {
-      setSortColumn(column);
-      setSortOrder('asc');
-      const sorted = [...originalData].sort((a, b) => {
-        return a[column] < b[column] ? -1 : 1;
-      });
-      setSortedData(sorted);
-    }
-  };
-
-  // Calculate the startIndex and endIndex based on currentPage and itemsPerPage
-  const startIndex = (currentPage - 1) * itemsPerPage;
-  const endIndex = startIndex + itemsPerPage;
-  const currentData = sortedData.slice(startIndex, endIndex);
-
-  function isString(value: any): boolean {
-    return typeof value === 'string';
-  }
-
-  return (
-    <div className = "text-center font-inter text-primary">
-      <table className="text-center my-2 mx-auto table-fixed w-11/12" >
-        <thead className="thead">
-          <tr className="bg-white text-center p-0 pl-2 border-0 h-2 font-weight: 700">
-            {keys.map((item, index) => (
-              <th
-                className="th"
-                key={index}
-                onClick={() => toggleSort(item)}
-              >
-                {item}{' '}
-                {sortColumn === item && (
-                  <span>{sortOrder === 'asc' ? 'â–²' : 'â–¼'}</span>
-                )
-                }
-              </th>
-            ))}
-          </tr>
-        </thead>
-        <tbody className="border-l-2 border-t-2 border-r-2 border-b-2 border-white w-20">
-          {currentData.map((obj, index) => (
-            <tr key={index} className={index % 2 === 0 ? "bg-secondary" : "bg-base-100"}>
-              {keys.map((item, index) => (
-                <td className={`${isString(obj[item]) ? 'text-left' : 'text-center'} px-1 py-0.5 border border-white m-0 overflow-x-hidden truncate`}
-                    key={index}
-                    >
-                    {obj[item]}
-                </td>
-              ))
-              }
-            </tr>
-          ))}
-        </tbody>
-      </table>
-
-      <Pagination
-        currentPage={currentPage}
-        totalPages={totalPages}
-        onPageChange={onPageChange}
-        itemsPerPageInput = {itemsPerPage}
-        numItemsArrayReal = {startIndex+currentData.length}
-        totalItems={sortedData.length}
-      />
-      
-      
-    </div>
-  );
-};
-
-export default Table;
diff --git a/libs/shared/lib/vis/simple_table_pagination/simpleTable.tsx b/libs/shared/lib/vis/simple_table_pagination/simpleTable.tsx
deleted file mode 100644
index 847a6895c4edc4f501312f4f571e7efb66e69305..0000000000000000000000000000000000000000
--- a/libs/shared/lib/vis/simple_table_pagination/simpleTable.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { useGraphQueryResult } from '../../data-access/store';
-import React, { useRef } from 'react';
-import Table from './components/Table';
-
-export const SimpleTableVis = React.memo(() => {
-
-  const ref = useRef<HTMLDivElement>(null);
-
-  const graphQueryResult = useGraphQueryResult();
-
-  const nodes = graphQueryResult.nodes;
-  const attributesArray = nodes.map((node) => node.attributes);
-
-  return (
-    <>
-      <div className="h-full w-full overflow-hidden" ref={ref}>
-        {attributesArray.length > 0 && <Table data={attributesArray} itemsPerPage ={10} />}
-      </div>
-    </>
-  );
-});
-
-SimpleTableVis.displayName = 'SimepleTableVis';
-
-export default SimpleTableVis;
diff --git a/libs/shared/lib/vis/table_vis/components/BarPlot.tsx b/libs/shared/lib/vis/table_vis/components/BarPlot.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..ade23d96320b730a5cf7129cc4ea1663529a33c4
--- /dev/null
+++ b/libs/shared/lib/vis/table_vis/components/BarPlot.tsx
@@ -0,0 +1,150 @@
+import React, { LegacyRef, useEffect, useRef, useState } from 'react';
+import * as d3 from 'd3';
+
+type BarPlotProps = {
+  data: { category: string; count: number }[];
+  numBins: number;
+  typeBarPlot: 'numerical' | 'categorical';
+};
+
+export const BarPlot = ({ typeBarPlot: typeBarplot, numBins, data }: BarPlotProps) => {
+  const svgRef = useRef<SVGSVGElement | null>(null);
+
+  useEffect(() => {
+    if (!svgRef.current) return;
+
+    const widthSVG: number = +svgRef.current.clientWidth;
+    const heightSVG: number = +svgRef.current.clientHeight;
+
+    const marginPercentage = { top: 0.15, right: 0.15, bottom: 0.15, left: 0.15 };
+    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;
+
+    // Barplot for categorical data
+    if (typeBarplot == 'categorical') {
+      // 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])
+        .domain(
+          dataSorted.map(function (d) {
+            return d.category;
+          })
+        )
+        .padding(0.2);
+
+      const yScale = d3.scaleLinear().domain([0, maxCount]).range([heightSVGwithinMargin, 0]).nice();
+
+      groupMargin
+        .selectAll<SVGRectElement, d3.Bin<string, number>>('barplotCats')
+        .data(dataSorted)
+        .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', 'fill-primary stroke-white');
+
+      const yAxis = d3.axisLeft(yScale).ticks(5);
+      groupMargin.append('g').call(yAxis);
+
+      // Barplot for numerical data
+    } else {
+      const dataCount = data.map((obj) => obj.count);
+
+      const extentData = d3.extent(dataCount);
+      const [min, max] = extentData as [number, number];
+
+      const xScale = d3.scaleLinear().range([0, widthSVGwithinMargin]).domain([min, max]).nice();
+
+      // d3.histogram -> deprecated
+      // On d3.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 rangeTH: number[] = d3.range(min, max, (max - min) / numBins);
+
+      if (rangeTH.length != numBins) {
+        rangeTH.pop();
+      }
+
+      const histogram = d3
+        .bin()
+        .value(function (d) {
+          return d;
+        })
+        .domain([min, max])
+        .thresholds(rangeTH);
+
+      const bins = histogram(dataCount);
+
+      const yScale = d3
+        .scaleLinear()
+        .range([heightSVGwithinMargin, 0])
+        .domain(d3.extent(bins, (d) => d.length) as [number, number]);
+
+      groupMargin
+        .selectAll<SVGRectElement, d3.Bin<number, number>>('barplotbins')
+        .data(bins)
+        .enter()
+        .append('rect')
+        .attr('x', 1)
+        .attr('class', 'fill-primary stroke-white')
+        .attr('transform', (d) => 'translate(' + xScale(d.x0 || 0) + ',' + yScale(d.length) + ')')
+        .attr('width', (d) => xScale(d.x1 || 0) - xScale(d.x0 || 0) - 1)
+        .attr('height', (d) => heightSVGwithinMargin - yScale(d.length));
+
+      const xAxis = d3.axisBottom(xScale).ticks(5);
+
+      groupMargin
+        .append('g')
+        .attr('transform', 'translate(0,' + heightSVGwithinMargin + ')')
+        .call(xAxis);
+    }
+
+    svgPlot.selectAll('.tick text').attr('class', 'font-inter text-primary font-semibold').style('stroke', 'none');
+
+    groupMargin
+      .append('rect')
+      .attr('x', 0.0)
+      .attr('y', 0.0)
+      .attr('width', widthSVGwithinMargin)
+      .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');
+  }, [data]);
+
+  return (
+    <div className="svg">
+      <svg ref={svgRef} className="container" width="100%" height="100%"></svg>
+    </div>
+  );
+};
+
+export default BarPlot;
diff --git a/libs/shared/lib/vis/table_vis/components/Pagination.tsx b/libs/shared/lib/vis/table_vis/components/Pagination.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..8d55c2cbf40752594d808e2eb9515fbce27fd2c3
--- /dev/null
+++ b/libs/shared/lib/vis/table_vis/components/Pagination.tsx
@@ -0,0 +1,54 @@
+import React, { useRef } from 'react';
+import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos';
+import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
+
+interface PaginationProps {
+  currentPage: number;
+  totalPages: number;
+  onPageChange: (page: number) => void;
+  itemsPerPageInput: number;
+  numItemsArrayReal: number;
+  totalItems: number;
+}
+
+const Pagination: React.FC<PaginationProps> = ({
+  currentPage,
+  totalPages,
+  onPageChange,
+  itemsPerPageInput,
+  numItemsArrayReal,
+  totalItems,
+}) => {
+  const pageNumbers = Array.from({ length: totalPages }, (_, index) => index + 1);
+
+  const firstItem = (currentPage - 1) * itemsPerPageInput + 1;
+  const lastItem = Math.min(currentPage * itemsPerPageInput, totalPages);
+
+  const goToPreviousPage = () => {
+    if (currentPage > 1) {
+      onPageChange(currentPage - 1);
+    }
+  };
+
+  const goToNextPage = () => {
+    if (currentPage < totalPages) {
+      onPageChange(currentPage + 1);
+    }
+  };
+
+  return (
+    <div className="table-pagination h-full flex flex-col items-center">
+      <span className="inline-block m-2 max-w-32 min-w-32">{`${firstItem}-${numItemsArrayReal} of ${totalItems}`}</span>
+      <div className="join">
+        <button className="m-1 btn btn-outline" onClick={goToPreviousPage} disabled={currentPage === 1}>
+          <ArrowBackIosIcon /> Previous
+        </button>
+        <button className="m-1 btn btn-outline" onClick={goToNextPage} disabled={currentPage === totalPages}>
+          <ArrowForwardIosIcon /> Next
+        </button>
+      </div>
+    </div>
+  );
+};
+
+export default Pagination;
diff --git a/libs/shared/lib/vis/table_vis/components/Table.tsx b/libs/shared/lib/vis/table_vis/components/Table.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..51d37a47ec2d8323b59b7434c99e1ff68aa26e37
--- /dev/null
+++ b/libs/shared/lib/vis/table_vis/components/Table.tsx
@@ -0,0 +1,208 @@
+import React, { useState, useEffect, useRef, useMemo } from 'react';
+import * as d3 from 'd3';
+import Pagination from './Pagination';
+import BarPlot from './BarPlot';
+import { NodeAttributes } from '@graphpolaris/shared/lib/data-access/store/graphQueryResultSlice';
+import { SchemaAttributeTypes } from '@graphpolaris/shared/lib/schema';
+
+export type AugmentedNodeAttributes = { attribute: NodeAttributes; type: Record<string, SchemaAttributeTypes> };
+
+type TableProps = {
+  data: AugmentedNodeAttributes[];
+  itemsPerPage: number;
+  showBarPlot: boolean;
+};
+type Data2RenderI = {
+  name: string;
+  typeData: SchemaAttributeTypes;
+  data: { category: string; count: number }[];
+  numElements: number;
+};
+
+export const Table = ({ data, itemsPerPage, showBarPlot }: TableProps) => {
+  const maxUniqueValues = 100;
+  const barPlotNumBins = 10;
+
+  const [sortedData, setSortedData] = useState<AugmentedNodeAttributes[]>(data);
+  const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc');
+  const [sortColumn, setSortColumn] = useState<string | null>(null);
+  const [currentPage, setCurrentPage] = useState<{
+    page: number;
+    startIndex: number;
+    endIndex: number;
+    currentData: AugmentedNodeAttributes[];
+  } | null>(null);
+  const [data2Render, setData2Render] = useState<Data2RenderI[]>([]);
+  const dataColumns = useMemo(() => Object.keys(data[0].attribute), [data]);
+  const totalPages = Math.ceil(sortedData.length / itemsPerPage);
+
+  useEffect(() => {
+    if (sortColumn !== null) {
+      const sorted = [...data].sort((a, b) => {
+        if (sortOrder === 'asc') {
+          return (a.attribute as any)[sortColumn] < (b.attribute as any)[sortColumn] ? -1 : 1;
+        } else {
+          return (a.attribute as any)[sortColumn] > (b.attribute as any)[sortColumn] ? -1 : 1;
+        }
+      });
+      setSortedData(sorted);
+    }
+  }, [sortOrder, data, sortColumn]);
+
+  useEffect(() => {
+    onPageChange(1); // Reset to the first page when sorting or itemsPerPage changes
+  }, [sortColumn, sortOrder, itemsPerPage]);
+
+  const onPageChange = (page: number) => {
+    const startIndex = (page - 1) * itemsPerPage;
+    const endIndex = startIndex + itemsPerPage;
+    const currentData = sortedData.slice(startIndex, endIndex);
+
+    setCurrentPage({
+      page: page,
+      startIndex: startIndex,
+      endIndex: endIndex,
+      currentData: currentData,
+    });
+  };
+
+  const toggleSort = (column: string) => {
+    if (sortColumn === column) {
+      if (sortOrder === 'asc') {
+        setSortOrder('desc');
+        setSortedData(sortedData.reverse()); // Reverse the sorted data
+      } else if (sortOrder === 'desc') {
+        setSortColumn(null);
+        setSortOrder('asc');
+        setSortedData(data); // Reset to the original order
+      }
+    } else {
+      setSortColumn(column);
+      setSortOrder('asc');
+      const sorted = [...data].sort((a, b) => {
+        return (a.attribute as any)[column] < (b.attribute as any)[column] ? -1 : 1;
+      });
+      setSortedData(sorted);
+    }
+  };
+
+  // Barplot on headers data preparation
+
+  // Data structure to feed the keys-barplot ( name data2Render )
+  // Three options:
+  // 1) numeric->data to histogram barplot
+  // 2) string with unique values < maxUniqueValues -> data binned barplot
+  // 3) string with unique values > maxUniqueValues -> show text unique values
+
+  useEffect(() => {
+    if (!currentPage || currentPage?.currentData?.length <= 0) return;
+
+    let categoryCounts = [];
+    const firstRowData = currentPage.currentData[0];
+
+    let _data2Render = Object.keys(firstRowData.attribute).map((dataColumn: string, i) => {
+      const newData2Render: Data2RenderI = {
+        name: dataColumn,
+        typeData: firstRowData.type[dataColumn] || 'string',
+        data: [],
+        numElements: 0,
+      };
+
+      if (firstRowData.type[dataColumn] === 'string') {
+        const groupedData = d3.group(data, (d) => d.attribute[dataColumn]);
+
+        categoryCounts = Array.from(groupedData, ([category, items]) => ({
+          category: category as string,
+          count: items.length,
+        }));
+
+        if (categoryCounts.length > maxUniqueValues) {
+          newData2Render.numElements = categoryCounts.length;
+        } else {
+          newData2Render.numElements = categoryCounts.length;
+          newData2Render.data = categoryCounts;
+        }
+
+        // number
+        // perhaps check for other than string and number. eg. boolean
+      } else {
+        categoryCounts = data.map((obj) => ({
+          category: 'placeholder', // add something
+          count: obj.attribute[dataColumn] as number, // fill values of data
+        }));
+
+        // console.log('number', categoryCounts);
+        newData2Render.numElements = categoryCounts.length;
+        newData2Render.data = categoryCounts;
+      }
+      return newData2Render;
+    });
+
+    setData2Render(_data2Render);
+  }, [currentPage, data, sortedData]);
+
+
+  return (
+    <div className="text-center font-inter text-primary">
+      {currentPage && currentPage?.currentData?.length > 0 && data2Render?.length > 0 && (
+        <>
+          <table className="text-center my-2 mx-auto table-fixed w-11/12">
+            <thead className="thead">
+              <tr className="bg-white text-center p-0 pl-2 border-0 h-2 font-weight: 700">
+                {dataColumns.map((item, index) => (
+                  <th className="th cursor-pointer select-none" key={index + item} onClick={() => toggleSort(item)}>
+                    {item} {sortColumn === item && <span>{sortOrder === 'asc' ? 'â–²' : 'â–¼'}</span>}
+                  </th>
+                ))}
+              </tr>
+              <tr className="svggs align-top">
+                {dataColumns.map((item, index) => (
+                  <th className="th" key={index + item}>
+                    <div className="h-full w-full overflow-hidden">
+                      {showBarPlot && (data2Render[index].typeData === 'int' || data2Render[index].typeData === 'float') ? (
+                        <BarPlot typeBarPlot='numerical' numBins={barPlotNumBins} data={data2Render[index].data} />
+                      ) : !showBarPlot || data2Render[index].numElements > maxUniqueValues ? (
+                        <div className="h-full text-xs font-normal flex flex-row items-center justify-center gap-1">
+                          <span>Unique values:</span>
+                          <span className="text-sm">{data2Render[index].numElements}</span>
+                        </div>
+                        ) : (
+                        <BarPlot typeBarPlot='categorical' numBins={barPlotNumBins} data={data2Render[index].data} />
+                      )}
+                    </div>
+                  </th>
+                ))}
+              </tr>
+            </thead>
+            <tbody className="border-l-2 border-t-2 border-r-2 border-b-2 border-white w-20">
+              {currentPage.currentData.map((item, index) => (
+                <tr key={index} className={index % 2 === 0 ? 'bg-base-200' : 'bg-base-100'}>
+                  {dataColumns.map((col, index) => (
+                    <td
+                      className={`${item.type[col] === 'string' ? 'text-left' : 'text-center'
+                      } px-1 py-0.5 border border-white m-0 overflow-x-hidden truncate`}
+                      key={index}
+                    >
+                      {item.attribute[col] as string}
+                    </td>
+                  ))}
+                </tr>
+              ))}
+            </tbody>
+          </table>
+
+          <Pagination
+            currentPage={currentPage.page}
+            totalPages={totalPages}
+            onPageChange={onPageChange}
+            itemsPerPageInput={itemsPerPage}
+            numItemsArrayReal={currentPage.startIndex + currentPage.currentData.length}
+            totalItems={sortedData.length}
+          />
+        </>
+      )}
+    </div>
+  );
+};
+
+export default Table;
diff --git a/libs/shared/lib/vis/table_vis/tableVis.tsx b/libs/shared/lib/vis/table_vis/tableVis.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..d9c8a89dc38b3c67c2b22edcd206d672ab275476
--- /dev/null
+++ b/libs/shared/lib/vis/table_vis/tableVis.tsx
@@ -0,0 +1,40 @@
+import { useGraphQueryResult, useSchemaGraph } from '../../data-access/store';
+import React, { useMemo, useRef } from 'react';
+import {Table, AugmentedNodeAttributes} from './components/Table';
+import { NodeAttributes } from '../../data-access/store/graphQueryResultSlice';
+import { SchemaAttribute } from '../../schema';
+
+export const TableVis = React.memo(({ showBarplot }: { showBarplot: boolean }) => {
+  const ref = useRef<HTMLDivElement>(null);
+  const graphQueryResult = useGraphQueryResult();
+  const schema = useSchemaGraph();
+  console.log(schema);
+
+  const attributesArray = useMemo<AugmentedNodeAttributes[]>(
+    () =>
+      graphQueryResult.nodes.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]))),
+        };
+      }),
+    [graphQueryResult.nodes]
+  );
+
+  return (
+    <>
+      <div className="h-full w-full overflow-auto" ref={ref}>
+        {attributesArray.length > 0 && <Table data={attributesArray} itemsPerPage={10} showBarPlot={showBarplot} />}
+      </div>
+    </>
+  );
+});
+
+TableVis.displayName = 'TableVis';
+
+export default TableVis;
diff --git a/libs/shared/lib/vis/simple_table_pagination/simpleTablevis.stories.tsx b/libs/shared/lib/vis/table_vis/tablevis.stories.tsx
similarity index 91%
rename from libs/shared/lib/vis/simple_table_pagination/simpleTablevis.stories.tsx
rename to libs/shared/lib/vis/table_vis/tablevis.stories.tsx
index 780170c14863712464874d08c5a87738b7db0220..0edeea9fcc34bb6847c572ca5c991828da2dd538 100644
--- a/libs/shared/lib/vis/simple_table_pagination/simpleTablevis.stories.tsx
+++ b/libs/shared/lib/vis/table_vis/tablevis.stories.tsx
@@ -1,19 +1,19 @@
 import React from 'react';
 import { Meta } from '@storybook/react';
-import { SimpleTableVis } from './simpleTable';
+import { TableVis } from './tableVis';
 
 import { assignNewGraphQueryResult, graphQueryResultSlice, resetGraphQueryResults, store } from '../../data-access/store';
 import { configureStore } from '@reduxjs/toolkit';
 import { Provider } from 'react-redux';
 import { big2ndChamberQueryResult, smallFlightsQueryResults, mockLargeQueryResults, bigMockQueryResults } from '../../mock-data';
 
-const Component: Meta<typeof SimpleTableVis> = {
+const Component: Meta<typeof TableVis> = {
   /* 👇 The title prop is optional.
    * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading
    * to learn how to generate automatic titles
    */
   title: 'Components/Visualizations/SimpleTableVis',
-  component: SimpleTableVis,
+  component: TableVis,
   decorators: [
     (story) => (
       <Provider store={Mockstore}>
diff --git a/package.json b/package.json
index 778f83958109e3b23a70024a1aa954ef883abcb1..1014839342fbeced79f5cdf66e42457cb864ca6e 100644
--- a/package.json
+++ b/package.json
@@ -15,7 +15,7 @@
     "sb": "turbo run sb --no-daemon",
     "lint": "turbo run lint --no-daemon",
     "test": "turbo run test --no-daemon",
-    "push": "turbo run push --no-daemon",
+    "push": "turbo run lint test build-dev --no-daemon",
     "format": "prettier --write \"**/*.{ts,tsx,md,js}\"",
     "prepare": "husky install"
   },