Page Menu
Home
c4science
Search
Configure Global Search
Log In
Files
F120198353
TabulatorTable.js
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Subscribers
None
File Metadata
Details
File Info
Storage
Attached
Created
Wed, Jul 2, 14:45
Size
30 KB
Mime Type
text/x-java
Expires
Fri, Jul 4, 14:45 (1 d, 23 h)
Engine
blob
Format
Raw Data
Handle
27121229
Attached To
R13029 webapp_nextjs
TabulatorTable.js
View Options
// 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
Log In to Comment