Skip to content

Commit

Permalink
Merge branch 'develop' into staging
Browse files Browse the repository at this point in the history
  • Loading branch information
ahonestla committed Oct 18, 2023
2 parents 14610a3 + d73284c commit 4b0282d
Show file tree
Hide file tree
Showing 13 changed files with 297 additions and 175 deletions.
78 changes: 45 additions & 33 deletions client/src/layout/ClustersPanel.jsx
Original file line number Diff line number Diff line change
@@ -1,61 +1,73 @@
import '@react-sigma/core/lib/react-sigma.min.css';
import { Container, Title, Text, Col, Row, Badge, BadgeGroup } from '@dataesr/react-dsfr';
import { GetColorName } from 'hex-color-to-color-name';
import { communityGetTopicsCount } from '../utils/communityUtils';
import { fillAndSortCommunities } from '../utils/communityUtils';
import { COMMUNTIY_COLORS } from '../styles/colors';

export default function ClustersPanel({ graph, communities, publications }) {
if (!graph.order) return null;

// Fill communities
const filledCommunities = fillAndSortCommunities(communities, publications, { communitiesLimit: 6, authorsLimit: 10, topicsLimit: 5, typesLimit: 3 });
console.log(filledCommunities);

return (
<Container fluid className="fr-my-3w">
<Title as="h3">
{Object.keys(communities).length}
{Object.keys(filledCommunities).length}
{' '}
main clusters
</Title>
<Row gutters>
{Object.keys(communities).map((community) => (
<Col key={community} n="4">
{Object.entries(filledCommunities).map(([key, community]) => (
<Col key={key} n="4">
<div className="fr-card fr-card--shadow">
<p style={{ backgroundColor: COMMUNTIY_COLORS[community], color: '#f6f6f6' }}>
<p style={{ backgroundColor: COMMUNTIY_COLORS[key], color: '#f6f6f6' }}>
&nbsp;&nbsp;Community
{' '}
{GetColorName(COMMUNTIY_COLORS[community])}
{GetColorName(COMMUNTIY_COLORS[key])}
</p>
<div className="fr-card__body">
<Title as="h6">5 main topics</Title>
<ul>
{communityGetTopicsCount(communities[community], publications).map((topic) => (
<li key={topic[0]}>
{topic[0]}
{' '}
(
{topic[1]}
)
</li>
<BadgeGroup className="fr-mb-2w">
<Badge colorFamily="green-emeraude" text={`${community.publications.length} publications`} />
<Badge colorFamily="green-bourgeon" text={`${community.nodes.length} authors`} />
</BadgeGroup>
<Title as="h6">
{community.topics.length}
{' '}
main topics
</Title>
<BadgeGroup className="fr-mb-2w">
{community.topics.map(([topic, count]) => (
<Badge type="info" text={`${topic} (${count})`} />
))}
</ul>
</BadgeGroup>
<Title as="h6">
{Math.min(10, communities[community].length)}
{community.authors.length}
{' '}
main authors
</Title>
{Object.values(communities[community]).map((node) => (
<>
<Text bold className="fr-mb-1v" key={node.key}>
{node.attributes.name}
</Text>
<BadgeGroup>
<Badge
colorFamily="purple-glycine"
className="fr-ml-1w"
text={`${node.attributes.weight} publications`}
/>
<Badge className="fr-ml-1w" text={`${node.attributes.size} co-author(s)`} />
</BadgeGroup>
</>
))}
<BadgeGroup className="fr-mb-2w">
{community.authors.map((node) => (
<Badge
colorFamily="purple-glycine"
text={`${node.attributes.name} (${node.attributes.weight})`}
/>
))}
</BadgeGroup>
<Title as="h6">
{community.types.length}
{' '}
main types
</Title>
<BadgeGroup className="fr-mb-2w">
{community.types.map(([type, count]) => (
<Badge
colorFamily="yellow-tournesol"
text={`${type} (${count})`}
/>
))}
</BadgeGroup>
</div>
</div>
</Col>
Expand Down
11 changes: 5 additions & 6 deletions client/src/layout/NodePanel.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import '@react-sigma/core/lib/react-sigma.min.css';
import { Badge, BadgeGroup, Title, Accordion, AccordionItem } from '@dataesr/react-dsfr';
import { publicationGetTopicsCount } from '../utils/publicationUtils';
import { publicationsGetTopicsCount } from '../utils/publicationUtils';

export default function NodePanel({ selectedNode, graph, publications }) {
if (!selectedNode) return null;
Expand All @@ -9,7 +9,7 @@ export default function NodePanel({ selectedNode, graph, publications }) {
<div className="fr-card fr-card--shadow">
<div className="fr-my-2w fr-card__body">
<Title look="h6" as="p" className="fr-mb-1v">
{selectedNode.label}
{selectedNode.name}
</Title>
<BadgeGroup className="fr-mt-1w">
<Badge colorFamily="yellow-tournesol" text={`${selectedNode.id}`} />
Expand All @@ -26,14 +26,13 @@ export default function NodePanel({ selectedNode, graph, publications }) {
</AccordionItem>
<AccordionItem title={`${selectedNode.weight} publications`}>
{graph.getNodeAttribute(selectedNode.id, 'publications').map((publicationId) => (
<p>{publications[publicationId].title}</p>
<p>{publications[publicationId]?.title}</p>
))}
</AccordionItem>
</Accordion>
<BadgeGroup className="fr-mt-2w">
{graph.getNodeAttribute(selectedNode.id, 'publications')
.map((publicationId) => publicationGetTopicsCount(publications, publicationId)
.map((topic) => <Badge type="info" text={`${topic[0]} (${topic[1]})`} />))}
{publicationsGetTopicsCount(publications, graph.getNodeAttribute(selectedNode.id, 'publications'), 10)
.map((topic) => <Badge type="info" text={`${topic[0]} (${topic[1]})`} />)}
</BadgeGroup>
</div>
</div>
Expand Down
83 changes: 75 additions & 8 deletions client/src/utils/communityUtils.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,82 @@
export function communityGetTopicsCount(community, publications, limit = 0) {
const communityGetUniquePublications = (community) => (
community.reduce((acc, node) => [...acc, ...node.attributes.publications.flatMap((id) => (!acc.includes(id) ? id : []))], [])
);

function communityGetTopicsCount(community, publications, limit = 0) {
const topics = {};

// Count topics
community.forEach((node) => {
node.attributes.publications.forEach((publicationId) => {
publications[publicationId]?.topics.forEach(({ label }) => { topics[label] = topics[label] + 1 || 1; });
});
// Count topics from unique publication ids
community.reduce((acc, node) => [...acc, ...node.attributes.publications.flatMap((id) => (!acc.includes(id) ? id : []))], []).forEach((id) => {
publications[id]?.topics?.forEach(({ label }) => { topics[label] = topics[label] + 1 || 1; });
});

const numberOfTopics = Object.keys(topics).length;
if (numberOfTopics === 0) return [];

// Get max topics
// @todo
const endSlice = limit > 0 ? limit : numberOfTopics;
const topTopics = Object.assign(
...Object
.entries(topics)
.sort(({ 1: a }, { 1: b }) => b - a)
.slice(0, endSlice)
.map(([k, v]) => ({ [k]: v })),
);

return Object.entries(topTopics);
}

function communityGetTypesCount(community, publications, limit = 0) {
const types = {};

// Count types from unique publication ids
community.reduce((acc, node) => [...acc, ...node.attributes.publications.flatMap((id) => (!acc.includes(id) ? id : []))], []).forEach((id) => {
types[publications[id].type] = types[publications[id].type] + 1 || 1;
});

const numberOfTypes = Object.keys(types).length;
// console.log('numberOfTopics', numberOfTopics);

if (numberOfTypes === 0) return [];

// Get max types
const endSlice = limit > 0 ? limit : numberOfTypes;
const topTypes = Object.assign(
...Object
.entries(types)
.sort(({ 1: a }, { 1: b }) => b - a)
.slice(0, endSlice)
.map(([k, v]) => ({ [k]: v })),
);

return Object.entries(topTypes);
}

function communityGetBestAuthors(community, limit = 0) {
const endSlice = limit > 0 ? limit : community.length;
// Count and sort coauthors
return community.sort((a, b) => b.attributes.publications.length - a.attributes.publications.length).slice(0, endSlice);
}

export function fillAndSortCommunities(communities, publications, { communitiesLimit = 0, topicsLimit = 0, typesLimit = 0, authorsLimit = 0 }) {
const filledCommunities = {};
const numberOfCommunities = Object.keys(communities).length;
const endSlice = communitiesLimit > 0 ? communitiesLimit : numberOfCommunities;

// Sort communities
const sortedCommunities = Object.entries(communities).sort((a, b) => b[1].length - a[1].length).slice(0, endSlice);

// Fill communities
sortedCommunities.forEach(([key, values]) => {
filledCommunities[key] = {
nodes: values,
size: values.length,
publications: communityGetUniquePublications(values),
topics: communityGetTopicsCount(values, publications, topicsLimit),
types: communityGetTypesCount(values, publications, typesLimit),
authors: communityGetBestAuthors(values, authorsLimit),
};
});

return Object.entries(topics);
return filledCommunities;
}
12 changes: 6 additions & 6 deletions client/src/utils/graphUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@ export const graphEncodeToJson = (data) => {
// Add nodes
data.nodes.forEach((node) => {
items.push({
id: node?.key,
label: node?.attributes?.label,
cluster: node?.attributes?.community + 1,
weights: { Works: node?.attributes?.weight, Topics: node?.attributes?.topics.length },
scores: { 'Topics/work ': node?.attributes?.topics.length / (node?.attributes?.weight || 1) },
id: node.key,
label: node.attributes?.label,
cluster: (node.attributes?.community ?? 0) + 1,
weights: { Works: node.attributes?.weight, Topics: node?.attributes?.topics.length },
scores: { 'Topics/work ': (node.attributes?.topics.length ?? 0) / (node.attributes?.weight || 1) },
});
});

// Add edges
data.edges.forEach((edge) => {
links.push({ source_id: edge?.source, target_id: edge?.target, strength: edge?.attributes?.weight });
links.push({ source_id: edge?.source, target_id: edge?.target, strength: edge.attributes?.weight });
});

const network = { network: { items, links } };
Expand Down
27 changes: 21 additions & 6 deletions client/src/utils/publicationUtils.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,28 @@
export function publicationGetTopicsCount(publications, id, limit = 0) {
export function publicationsGetTopicsCount(publications, publicationsIds, limit = 0) {
const topics = {};

// Count topics
publications[id]?.topics.forEach(({ label }) => {
topics[label] = topics[label] + 1 || 1;
});
publicationsIds.forEach((publicationId) => (
publications[publicationId]?.topics.forEach(({ label }) => {
topics[label] = topics[label] + 1 || 1;
})));

const numberOfTopics = Object.keys(topics).length;
// console.log('numberOfTopics', numberOfTopics);

if (numberOfTopics === 0) return [];

console.log('topics: ', topics);

// Get max topics
// @todo
const endSlice = limit > 0 ? limit : numberOfTopics;
const topTopics = Object.assign(
...Object
.entries(topics)
.sort(({ 1: a }, { 1: b }) => b - a)
.slice(0, endSlice)
.map(([k, v]) => ({ [k]: v })),
);

return Object.entries(topics);
return Object.entries(topTopics);
}
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"graphology-layout": "^0.6.1",
"graphology-layout-forceatlas2": "^0.10.1",
"graphology-layout-noverlap": "^0.4.2",
"graphology-metrics": "^2.2.0",
"graphology-operators": "^1.6.0",
"winston": "^3.8.2",
"yamljs": "^0.3.0"
Expand Down
Loading

0 comments on commit 4b0282d

Please sign in to comment.