Page MenuHomec4science

TabulatorTable.js
No OneTemporary

File Metadata

Created
Tue, Jan 28, 18:01

TabulatorTable.js

// components/TabulatorComponent.js
import {useEffect, useRef, useState} from 'react';
import 'tabulator-tables/dist/css/tabulator.min.css';
import React from "react";
import {ReactTabulator} from 'react-tabulator';
import "font-awesome/css/font-awesome.min.css";
import ReactDOMServer from 'react-dom/server';
import './TabulatorTable.css'; // Import your custom CSS
import process from "next/dist/build/webpack/loaders/resolve-url-loader/lib/postcss";
function customHeaderFilter(headerValue, rowValue, rowData, filterParams) {
//headerValue - the value of the header filter element
//rowValue - the value of the column in this row
//rowData - the data for the row being filtered
//filterParams - params object passed to the headerFilterFuncParams property
// no filter applied
if (headerValue.length === 0) return true
if (typeof headerValue === 'string') {
headerValue = [headerValue]
}
headerValue = headerValue.map(function (x) {
if (x in reverseDicoTranslatePosition) {
return reverseDicoTranslatePosition[x]
} else {
return x
}
});
// filters applied
result = false;
for (let val of headerValue) {
if (String(rowValue).includes(val + " ")) {
result = true
} else if (String(rowValue).includes(" " + val)) {
result = true
} else if (String(rowValue).includes(val + ',')) {
result = true
} else if (rowValue === val) {
result = true
}
if (val === "Other") {
var commonPositions = ["Prof (PO)",
"Prof (PA)",
"Prof (PATT)",
"Prof (PH)",
"Prof (PT)",
"MER",
"Scientist",
"PhD",
"PostDoc",
"Engineer",
"Assistant",
"Lecturer"]
if (!commonPositions.some(position => rowValue.includes(position))) {
var result = true;
}
}
}
return result; //must return a boolean, true if it passes the filter.
}
function customHeaderFilter2(headerValue, rowValue, rowData, filterParams) {
//headerValue - the value of the header filter element
//rowValue - the value of the column in this row
//rowData - the data for the row being filtered
//filterParams - params object passed to the headerFilterFuncParams property
// no filter applied
if (headerValue.length === 0) return true
if (typeof headerValue === 'string') {
headerValue = [headerValue]
}
let result = false;
for (let val of headerValue) {
if (String(rowValue).includes(val)) {
result = true
}
}
return result; //must return a boolean, true if it passes the filter.
}
function sortDataByScoreIndex(data, index) {
return data.sort((a, b) => b.scores[index] - a.scores[index]);
}
function sortDataByMeanScore(data) {
return data.sort((a, b) => b.meanScore - a.meanScore);
}
function handleBarClick(event, cell) {
const target = event.target;
const columnIndex = target.getAttribute('data-index');
if (columnIndex !== null) {
const table = cell.getTable();
const originalData = table.getData();
const sortedData = sortDataByScoreIndex(originalData, columnIndex);
table.replaceData(sortedData);
}
}
function handleMeanBarClick(event, cell) {
const table = cell.getTable();
const originalData = table.getData();
const sortedData = sortDataByMeanScore(originalData)
table.replaceData(sortedData);
}
function createScoresBarChartFormatter(
setToolTipVisible,
setToolTipContent,
searchData
) {
return function scoresBarChartFormatter(cell, formatterParams, onRendered) {
const scores = cell.getValue();
const container = document.createElement('div');
container.style.display = 'flex';
container.style.alignItems = 'flex-end';
container.style.height = '100%';
const proportion = 1 / scores.length * 100;
scores.forEach((score, index) => {
const barContainer = document.createElement('div');
barContainer.style.position = 'relative';
barContainer.style.width = proportion + "%"; // Width of each bar container
barContainer.style.maxWidth = '100px';
barContainer.style.marginRight = '5px'; // Space between bar containers
barContainer.style.height = '100%'; // Height of bar container to fill the full height
const bar = document.createElement('div');
bar.style.width = '100%'; // Width of the bar to fill the container
bar.style.height = `${(score) * 100}%`; // Height relative to the maximum score
bar.style.backgroundColor = 'blue'; // Color of the bars
bar.style.position = 'absolute';
bar.style.bottom = '0';
const overlay = document.createElement('div');
overlay.style.position = 'absolute';
overlay.style.top = '0';
overlay.style.left = '0';
overlay.style.width = '100%'; // Cover the entire container width
overlay.style.height = '100%'; // Cover the entire container height
overlay.style.cursor = 'pointer'; // Change cursor to indicate clickable area
overlay.setAttribute('data-index', index); // Setting index as a data attribute
overlay.setAttribute('data-score', score); // Setting score as a data attribute
overlay.addEventListener('mouseenter', () => {
setToolTipVisible(true);
setToolTipContent(searchData['tableColumnsInRightOrder'][index] + ': ' + (score * 100).toFixed(2));
});
overlay.addEventListener('mouseleave', () => {
setToolTipVisible(false);
});
barContainer.appendChild(bar);
barContainer.appendChild(overlay);
container.appendChild(barContainer);
});
return container;
}
}
function rowFormatter(cell, formatterParams, onRendered) {
const scores = cell.getValue();
const columnTitle = cell.getColumn().getDefinition().title;
let toolTipFunctionName = columnTitle === 'Mean' ? 'showToolTipMean' : 'showToolTip';
// Start with a flex container
let barsHTML = '<div style="display: flex; height: 100%;">';
const proportion = 1 / scores.length * 100;
scores.forEach((score, index) => {
const barHeight = `${(score) * 100}%`;
barsHTML += `
<div style="position: relative; width: ${proportion}%; max-width: 100px; margin-right: 5px; height: 100%; min-height:29px ">
<div style="width: 100%; height: ${barHeight}; background-color: blue; position: absolute; bottom: 0;"></div>
<div
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; cursor: pointer;"
data-index="${index}"
data-score="${score}"
onmouseenter="${toolTipFunctionName}(event, ${index}, ${score})"
onmouseleave="hideToolTip()"></div>
</div>
`;
});
// Close the flex container
barsHTML += '</div>';
return barsHTML;
}
const createExpandedInfo = (response, row, rowData, searchEntity) => {
let expandedInfoContent = '';
if (searchEntity === 'courses') {
const responseData = Object.values(response)[0];
expandedInfoContent += `<p><b>Summary:</b><br/>${responseData ['Summary_translated']}</p>`;
} else if (searchEntity === 'publications') {
const responseData = Object.values(response)[0];
expandedInfoContent += `<p><b>Abstract:</b><br/>${responseData ['Abstract']}</p>`;
} else {
expandedInfoContent += `<p><b>Related Publications:</b><br/>`;
for (let pub in response['pubs']) {
expandedInfoContent += `<a href="https://infoscience.epfl.ch/record/${pub}?ln=en">${response['pubs'][pub]['Title']}</a><br/>`;
}
expandedInfoContent += `</p>`;
expandedInfoContent += `<p><b>Related Courses:</b><br/>`;
for (let course in response['courses']) {
expandedInfoContent += `<a href="https://search.epfl.ch/?filter=courses&q=${course}" target="_blank">${response['courses'][course]['SubjectName']}</a><br/>`;
}
expandedInfoContent += `</p>`;
}
// Check if the expanded-info div already exists
let expandedInfo = row.getElement().querySelector('.expanded-info');
if (!expandedInfo) {
expandedInfo = document.createElement('div');
expandedInfo.classList.add('expanded-info');
row.getElement().appendChild(expandedInfo);
}
expandedInfo.innerHTML = expandedInfoContent;
expandedInfo.style.display = 'block';
row.normalizeHeight();
};
const handleCellClick = async (cell,
searchEntity,
clickRowRunning,
setClickRowRunning,
conceptsOrVectors,
searchTerm) => {
console.log('handleCellClick, searchTerm', searchTerm)
if (!['ID', 'Links', 'meanScore', 'scores'].includes(cell.getColumn().getField())) {
const row = cell.getRow();
const rowData = row.getData();
let expandedInfo = row.getElement().querySelector('.expanded-info');
if (expandedInfo) {
if (expandedInfo.style.display === 'block') {
expandedInfo.style.display = 'none';
row.normalizeHeight();
return;
} else {
expandedInfo.style.display = 'block';
row.normalizeHeight();
}
}
// ... (the rest of your existing logic here)
if (!clickRowRunning) {
setClickRowRunning(true);
try {
const response = await fetch("http://127.0.0.1:5000/vectors/clickOnRow", {
// const response = await fetch("https://expert-finder.epfl.ch/vectors/clickOnRow", {
method: "POST",
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
ID: rowData.ID,
num_results: 5,
// num_results: document.getElementById("numResults").value,
research_texts: searchTerm,
entity_type: searchEntity,
}),
});
const responseData = await response.json();
createExpandedInfo(responseData, row, rowData, searchEntity);
} catch (error) {
console.error("Error during fetch: ", error);
} finally {
setClickRowRunning(false);
}
}
}
};
const TabulatorComponent = ({
searchData,
searchEntity,
setToolTipVisible,
setToolTipContent,
setDataDisplayed,
conceptsOrVectors,
searchTerm
}) => {
console.log('render TabulatorComponent')
const data = searchData['table']
const [isTableVisible, setIsTableVisible] = useState(false);
const tableRef = useRef(null);
const [processedData, setProcessedData] = useState([]);
const [clickRowRunning, setClickRowRunning] = useState(false);
useEffect(() => {
if (searchData) {
const newData = searchData['table'].map((d) => {
const newD = {...d, scores: []};
searchData['tableColumnsInRightOrder'].forEach((column) => {
newD['scores'].push(d[column]);
});
if (conceptsOrVectors === 'vectors') {
newD['meanScore'] = [
Math.pow(
newD['scores'].reduce((a, b) => a * b, 1),
1 / newD['scores'].length
),
];
} else {
newD['meanScore'] = [
newD['scores'].reduce((a, b) => a + b, 0) / newD['scores'].length
];
}
return newD;
});
setProcessedData(newData);
}
}, [searchData,
// tableColumnsInRightOrder
]);
const downloadCSV = () => {
if (tableRef.current) {
tableRef.current.download("csv", "data.csv", {
columns: "all", // this will include both visible and hidden columns in the download
});
}
};
return (
<div>
<button
onClick={() => setIsTableVisible(prevState => !prevState)}
className="btn btn-primary"
style={{margin: '10px'}}
>
{isTableVisible ? "Hide Table" : "Show Table"}
</button>
<div style={{display: isTableVisible ? 'block' : 'none'}}>
<div className="transparent-tabulator">
<ReactTabulator
onRef={(ref) => {
tableRef.current = ref?.current;
}}
data={processedData}
columns={initializeFixedColumns(
searchEntity,
setToolTipVisible,
setToolTipContent,
searchData
)}
options={{
layout: 'fitColumns', pagination: true, paginationSize: 15,
persistence: {
headerFilter: true, //persist header filters
columns: true, //persist column widths
},
reactiveData: true,
}}
events={{
dataFiltered: function (filters, rows) {
const data = rows.map(row => row.getData());
setDataDisplayed(data);
},
cellClick: (e, cell) =>
handleCellClick(cell,
searchEntity,
clickRowRunning,
setClickRowRunning,
'vectors',
searchTerm
),
}}
/>
<div style={{display: 'flex', justifyContent: 'flex-end', margin: '10px'}}>
<button id="download-csv" className="btn btn-primary"
onClick={downloadCSV}
>
Download CSV
</button>
</div>
</div>
</div>
</div>
);
};
// export default TabulatorComponent;
export default React.memo(TabulatorComponent);
function initializeFixedColumns(searchEntity, setToolTipVisible, setToolTipContent,
// tableColumnsInRightOrder,
searchData
) {
let columnsInit;
if (searchEntity === 'people' || searchEntity === 'teaching' || searchEntity === 'research') {
columnsInit = [ // fixed columns
{
title: "Links",
headerHozAlign: "center",
formatter: linkIcons,
width: linkWidth,
headerSort: false,
cellMouseOver: (e, cell) => {
// Get the closest parent DIV to the target
const closestDiv = e.target.closest('div');
if (!closestDiv) {
return;
}
const data = cell.getData();
// Find the index of the closest parent DIV in its parent's children
const iconIndex = [...closestDiv.parentElement.children].indexOf(closestDiv);
switch (iconIndex) {
case 0:
setToolTipContent('people.epfl.ch');
setToolTipVisible(true);
break;
case 1:
setToolTipVisible(true);
setToolTipContent('Linkedin');
break;
case 2:
setToolTipVisible(true);
setToolTipContent('Google News');
break;
case 3:
setToolTipVisible(true);
setToolTipContent('Memento');
break;
case 4:
setToolTipVisible(true);
setToolTipContent('EPFL News');
break;
default:
break;
}
},
cellClick: (e, cell) => {
const closestDiv = e.target.closest('div');
if (!closestDiv) {
return;
}
const data = cell.getRow().getData();
// Note: Getting the parent of the closest div and then finding the index of the closest div among its siblings.
const iconIndex = [...closestDiv.parentElement.children].indexOf(closestDiv);
switch (iconIndex) {
case 0:
openModal(data.ID);
break;
case 1:
openLinkedin(data.FullName);
break;
case 2:
openGoogleNews(data.FullName);
break;
case 3:
openMemento(data.FullName);
break;
case 4:
openNews(data.FullName);
break;
default:
break;
}
},
cellMouseOut: () => {
//hide tooltip
hideToolTip();
}
},
{
title: "Name",
headerHozAlign: "center",
field: "FullName",
frozen: true,
width: FullNameWidth
},
{
title: "Gender",
headerHozAlign: "center",
field: "Gender ",
headerFilter: "list",
headerFilterParams: {
values: ["Male", "Female"],
multiselect: true
},
width: GenderWidth,
headerFilterFunc: customHeaderFilter,
headerSort: false,
},
{
title: "Position",
headerHozAlign: "center",
field: "Position",
// frozen:true,
width: PositionWidth,
headerFilter: "list",
headerFilterParams: {
values: [
"Prof (PO)",
"Prof (PA)",
"Prof (PATT)",
"Prof (PH)",
"Prof (PT)",
"MER",
"Scientist",
"PhD",
"PostDoc",
"Engineer",
"Assistant",
"Lecturer",
"Other",
],
multiselect: true
},
headerFilterFunc: customHeaderFilter
},
{
title: "Schools",
headerHozAlign: "center",
headerSort: false,
field: "Schools",
// frozen:true,
width: SchoolWidth,
headerFilter: "list",
headerFilterParams: {
values: ["CDH", "CDM", "IC", "ENAC", "SB", "STI", "SV"],
multiselect: true
},
headerFilterFunc: customHeaderFilter
},
{
title: "Labs",
headerHozAlign: "center",
headerSort: false,
field: "Labs",
width: LabWidth,
},
{
title: 'CF',
headerHozAlign: "center",
field: "CF",
visible: false,
download: true,
}
]
} else if (searchEntity === 'courses') {
columnsInit = [
{
title: "ID",
headerHozAlign: "center",
field: 'ID',
formatter: addLinkCourseID,
frozen: true,
width: courseCodeWidth
},
{
title: 'Level',
headerHozAlign: "center",
field: "Level",
frozen: true,
width: LevelWidth,
headerFilter: "list",
headerFilterParams: {
values: ["Bachelor", "Master", "PhD"],
multiselect: true
},
headerFilterFunc: customHeaderFilter2
},
{
title: "Title",
headerHozAlign: "center",
field: "SubjectName",
frozen: true,
width: FullNameWidth
},
{
title: "Schools",
headerHozAlign: "center",
headerSort: false,
field: "Schools",
// frozen:true,
width: SchoolWidth,
headerFilter: "list",
headerFilterParams: {
values: ["CDH", "CDM", "IC", "ENAC", "SB", "STI", "SV", 'Unknown'],
multiselect: true
},
headerFilterFunc: customHeaderFilter
},
{
title: "Section",
headerHozAlign: "center",
headerSort: false,
field: "SectionCode",
// frozen:true,
width: SchoolWidth,
},
]
} else if (['labs', 'lab'].includes(searchEntity.toLowerCase())) {
columnsInit = [
{
title: "ID",
headerHozAlign: "center",
field: "ID",
formatter: addLinkLabID,
// frozen: true,
width: labIDWidth
},
{
title: 'FullName',
headerHozAlign: "center",
field: "FullName",
frozen: true,
width: FullNameWidth,
headerSort: false
},
{
title: 'Head',
headerHozAlign: "center",
field: "HeadFullname",
// frozen: true,
width: FullNameWidth,
headerSort: false,
resizable: true
},
{
title: 'Schools',
headerHozAlign: "center",
field: "Schools",
frozen: true,
width: SchoolWidth
}
]
} else if (['publications', 'publication', 'pubs', 'pub'].includes(searchEntity.toLowerCase())) {
columnsInit = [
{
title: "Title",
headerHozAlign: "center",
field: "Title",
formatter: addLinkPubID,
frozen: true,
width: 500
},
{
title: "Schools",
headerHozAlign: "center",
headerSort: false,
field: "Schools",
// frozen:true,
width: SchoolWidth,
headerFilter: "list",
headerFilterParams: {
values: ["CDH", "CDM", "IC", "ENAC", "SB", "STI", "SV"],
multiselect: true
},
headerFilterFunc: customHeaderFilter
},
{
title: "Year",
headerHozAlign: "center",
field: "Year",
frozen: true,
width: yearWidth,
headerFilter: "input",
headerFilterPlaceholder: "YYYY-YYYY", // Add a placeholder
headerFilterFunc: function (headerValue, rowValue, rowData, filterParams) {
// split the headerValue into start and end year
var years = headerValue.split('-');
var startYear;
var endYear;
if (years.length === 1) {
startYear = parseInt(years[0]);
endYear = 3000;
} else if (years.length === 2) {
startYear = parseInt(years[0]);
endYear = parseInt(years[1]);
} else {
return false;
}
return rowValue >= startYear && rowValue <= endYear;
}
}
]
}
window.showToolTip = function (event, index, score) {
setToolTipVisible(true);
setToolTipContent(searchData['tableColumnsInRightOrder'][index] + ': ' + (score * 100).toFixed(2));
}
window.showToolTipMean = function (event, index, score) {
setToolTipVisible(true);
setToolTipContent('Mean: ' + (score * 100).toFixed(2));
}
window.hideToolTip = function () {
setToolTipVisible(false);
}
columnsInit.push(
{
title: 'Mean', field: 'meanScore',
// formatter: createScoresBarChartFormatter(setToolTipVisible, setToolTipContent, {
// 'tableColumnsInRightOrder': ['Mean'],
// }),
formatter: rowFormatter,
cellClick: handleMeanBarClick,
headerSort: false,
width: 100,
download: false
// widthGrow: 2,
},
)
columnsInit.push(
{
title: 'Scores', field: 'scores',
// formatter: createScoresBarChartFormatter(
// setToolTipVisible,
// setToolTipContent,
// // tableColumnsInRightOrder,
// searchData
// ),
formatter: rowFormatter,
cellClick: handleBarClick,
// Use the refactored logic in rowFormatter
headerSort: false,
download: false
// widthGrow: 2,
},
)
// tableColumnsInRightOrder.forEach((column, index) => {
searchData['tableColumnsInRightOrder'].forEach((column, index) => {
columnsInit.push({
title: column,
field: column,
visible: false,
download: true,
})
})
return columnsInit
}
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faInfo, faNewspaper} from '@fortawesome/free-solid-svg-icons';
import {faLinkedin} from '@fortawesome/free-brands-svg-icons';
import column from "tabulator-tables/src/js/core/column/Column";
import {hide} from "react-modal/lib/helpers/ariaAppHider";
function openModal(id) {
const url = 'https://people.epfl.ch/' + id;
window.open(url);
}
function openLinkedin(fullName) {
const url = "https://www.linkedin.com/search/results/people/?keywords=" + fullName + " Lausanne";
window.open(url);
}
function openMemento(fullName) {
const nameArray = fullName.split(' ');
let url = "https://memento.epfl.ch/epfl/?keywords=";
for (const name of nameArray) {
url += name + "+";
}
url = url.slice(0, url.length - 1);
url += "&search=";
window.open(url);
}
function openNews(fullName) {
const nameArray = fullName.split(' ');
let url = "https://news.epfl.ch/?keywords=";
for (const name of nameArray) {
url += name + "+";
}
url = url.slice(0, url.length - 1);
url += "&search=Search";
window.open(url);
}
function openGoogleNews(fullName) {
const url = "https://news.google.com/search?q=" + fullName;
window.open(url);
}
function linkIcons(cell, formatterParams) {
const icons = (
<div style={{display: 'flex'}}>
<div style={{marginLeft: '3px'}}><FontAwesomeIcon icon={faInfo}/> {" "}</div>
<div style={{marginLeft: '3px'}}><FontAwesomeIcon icon={faLinkedin}/> {" "}</div>
<div style={{marginLeft: '3px'}}><FontAwesomeIcon icon={faNewspaper}/> {" "}</div>
<div style={{marginLeft: '3px'}}><i className="fa fa-users"/> {" "}</div>
<div style={{marginLeft: '3px'}}><i className="fa fa-bell"/></div>
</div>
);
return ReactDOMServer.renderToString(icons);
}
const linkWidth = 100;
const FullNameWidth = 300;
const GenderWidth = 100;
const PositionWidth = 200;
const SchoolWidth = 100;
const LabWidth = 100;
const courseCodeWidth = 100;
const LevelWidth = 100;
const labIDWidth = 100;
const yearWidth = 100;
function addLinkCourseID(cell) {
var cellValue = cell.getValue();
return "<a href='https://search.epfl.ch/?filter=courses&q=" + cellValue + "' target='_blank'>" + cellValue + "</a>";
}
function addLinkLabID(cell) {
var cellValue = cell.getValue();
return "<a href='https://search.epfl.ch/?filter=unit&q=" + cellValue + "' target='_blank'>" + cellValue + "</a>";
}
function addLinkPubID(cell) {
var cellValue = cell.getValue();
let row = cell.getRow().getData();
let id = row['ID']
return "<a href='https://infoscience.epfl.ch/record/" + id + "' target='_blank'>" + cellValue + "</a>";
}
const reverseDicoTranslatePosition = {
"Prof (PO)": "Professeur ordinaire",
"Prof (PA)": "Professeur associé",
"Prof (PATT)": "Professeur assistant tenure track",
"Prof (PH)": "Professeur honoraire",
"Prof (PT)": "Professeur titulaire",
"MER": "Maître d'enseignement et de recherche",
"Scientist": "Collaborateur scientifique",
"PhD": "Assistant-doctorant",
"PostDoc": "Post-Doctorant",
"Engineer": "Ingénieur",
"Assistant": "Assistant",
"Lecturer": "Chargé de cours",
"Unknown": "Unknown",
}

Event Timeline