diff --git a/src/components/Graph/Graph.js b/src/components/Graph/Graph.js
new file mode 100644
index 00000000..6e795f9a
--- /dev/null
+++ b/src/components/Graph/Graph.js
@@ -0,0 +1,55 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+// we need to require from react-sigma/lib/ to make build work
+import {
+ Sigma,
+ EdgeShapes,
+ NodeShapes,
+ ForceAtlas2,
+ RandomizeNodePositions,
+} from 'react-sigma/lib/';
+
+const Graph = ({graph, settings, edgeShape}) => {
+ const layoutOptions = {
+ iterationsPerRender: 40,
+ edgeWeightInfluence: 0,
+ timeout: 2000,
+ adjustSizes: false,
+ gravity: 3,
+ slowDown: 5,
+ linLogMode: true,
+ outboundAttractionDistribution: false,
+ strongGravityMode: false,
+ };
+
+ const layout = ;
+
+ let sigma = null;
+ if (graph && graph.nodes.length > 0) {
+ sigma = (
+
+
+
+ {layout}
+
+ );
+ }
+
+ return sigma;
+};
+
+Graph.propTypes = {
+ graph: PropTypes.shape({
+ nodes: PropTypes.array.isRequired,
+ edges: PropTypes.array.isRequired,
+ }).isRequired,
+ settings: PropTypes.object.isRequired,
+ edgeShape: PropTypes.string.isRequired,
+};
+
+export default Graph;
diff --git a/src/components/Graph/NetworkGraph.js b/src/components/Graph/NetworkGraph.js
new file mode 100644
index 00000000..7a70d2b8
--- /dev/null
+++ b/src/components/Graph/NetworkGraph.js
@@ -0,0 +1,33 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import {makeGraph} from '../../network';
+
+import Graph from './Graph';
+
+const NetworkGraph = ({characters, play}) => {
+ const graph = makeGraph(characters, play, 'cooccurence');
+
+ const settings = {
+ maxEdgeSize: 5,
+ defaultLabelSize: 15,
+ defaultEdgeColor: '#61affe65', // FIXME: this does not seem to work
+ defaultNodeColor: '#61affe',
+ labelThreshold: 5,
+ labelSize: 'fixed',
+ drawLabels: true,
+ mouseWheelEnabled: false,
+ drawEdges: true,
+ };
+
+ return ;
+};
+
+NetworkGraph.propTypes = {
+ characters: PropTypes.array.isRequired,
+ play: PropTypes.shape({
+ relations: PropTypes.array.isRequired,
+ segments: PropTypes.array.isRequired,
+ }).isRequired,
+};
+
+export default NetworkGraph;
diff --git a/src/components/Graph/RelationsGraph.js b/src/components/Graph/RelationsGraph.js
new file mode 100644
index 00000000..296feebe
--- /dev/null
+++ b/src/components/Graph/RelationsGraph.js
@@ -0,0 +1,35 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import {makeGraph} from '../../network';
+import Graph from './Graph';
+
+const RelationsGraph = ({characters, play}) => {
+ const graph = makeGraph(characters, play, 'relation');
+
+ const settings = {
+ maxEdgeSize: 5,
+ defaultLabelSize: 14,
+ defaultEdgeColor: '#61affe65', // FIXME: this does not seem to work
+ defaultNodeColor: '#61affe',
+ edgeLabelColor: 'edge',
+ labelThreshold: 3,
+ labelSize: 'fixed',
+ drawLabels: true,
+ drawEdges: true,
+ drawEdgeLabels: true,
+ edgeLabelSize: 'proportional',
+ minArrowSize: 10,
+ };
+
+ return ;
+};
+
+RelationsGraph.propTypes = {
+ characters: PropTypes.array.isRequired,
+ play: PropTypes.shape({
+ relations: PropTypes.array.isRequired,
+ segments: PropTypes.array.isRequired,
+ }).isRequired,
+};
+
+export default RelationsGraph;
diff --git a/src/components/NetworkGraph.js b/src/components/NetworkGraph.js
deleted file mode 100644
index e60c40d7..00000000
--- a/src/components/NetworkGraph.js
+++ /dev/null
@@ -1,75 +0,0 @@
-import React, {Component} from 'react';
-import PropTypes from 'prop-types';
-// we need to require from react-sigma/lib/ to make build work
-import {
- Sigma,
- EdgeShapes,
- NodeShapes,
- ForceAtlas2,
- RelativeSize,
- RandomizeNodePositions,
-} from 'react-sigma/lib/';
-
-class NetworkGraph extends Component {
- render() {
- const {graph, nodeColor, edgeColor} = this.props;
-
- const settings = {
- maxEdgeSize: 5,
- defaultLabelSize: 15,
- defaultEdgeColor: edgeColor, // FIXME: this does not seem to work
- defaultNodeColor: nodeColor,
- labelThreshold: 5,
- labelSize: 'fixed',
- drawLabels: true,
- mouseWheelEnabled: false,
- drawEdges: true,
- };
-
- const layoutOptions = {
- iterationsPerRender: 40,
- edgeWeightInfluence: 0,
- timeout: 2000,
- adjustSizes: false,
- gravity: 3,
- slowDown: 5,
- linLogMode: true,
- outboundAttractionDistribution: false,
- strongGravityMode: false,
- };
-
- const layout = ;
-
- let sigma = null;
- if (graph && graph.nodes.length > 0) {
- sigma = (
-
-
-
-
- {layout}
-
-
-
- );
- }
-
- return sigma;
- }
-}
-
-NetworkGraph.propTypes = {
- graph: PropTypes.shape({
- nodes: PropTypes.array.isRequired,
- edges: PropTypes.array.isRequired,
- }).isRequired,
- nodeColor: PropTypes.string.isRequired,
- edgeColor: PropTypes.string.isRequired,
-};
-
-export default NetworkGraph;
diff --git a/src/components/Play.js b/src/components/Play.js
index 2f891abf..11969d9c 100644
--- a/src/components/Play.js
+++ b/src/components/Play.js
@@ -3,15 +3,14 @@ import PropTypes from 'prop-types';
import {Container} from 'reactstrap';
import {Helmet} from 'react-helmet';
import api from '../api';
-import {makeGraph} from '../network';
import PlayDetailsHeader from './PlayDetailsHeader';
import PlayDetailsNav from './PlayDetailsNav';
import PlayDetailsTab from './PlayDetailsTab';
import CastList from './CastList';
import SourceInfo from './SourceInfo';
import DownloadLinks from './DownloadLinks';
-import NetworkGraph from './NetworkGraph';
-import RelationsGraph from './RelationsGraph';
+import NetworkGraph from './Graph/NetworkGraph';
+import RelationsGraph from './Graph/RelationsGraph';
import SpeechDistribution, {SpeechDistributionNav} from './SpeechDistribution';
import TEIPanel from './TEIPanel';
import PlayMetrics from './PlayMetrics';
@@ -21,16 +20,6 @@ import './Play.scss';
const apiUrl = api.getBaseURL();
-const edgeColor = '#61affe65';
-const nodeColor = '#61affe';
-
-const nodeProps = (node) => {
- const {sex} = node;
- const color = sex === 'MALE' || sex === 'FEMALE' ? '#1f2448' : '#61affe';
- const type = sex === 'MALE' ? 'square' : 'circle';
- return {color, type};
-};
-
const navItems = [
{name: 'network', label: 'Network'},
{name: 'relations', label: 'Relations'},
@@ -43,7 +32,7 @@ const tabNames = new Set(navItems.map((item) => item.name));
const PlayInfo = ({corpusId, playId}) => {
const [play, setPlay] = useState(null);
- const [graph, setGraph] = useState(null);
+ const [characters, setCharacters] = useState(null);
const [error, setError] = useState(null);
const [chartType, setChartType] = useState('sapogov');
@@ -55,10 +44,7 @@ const PlayInfo = ({corpusId, playId}) => {
try {
const response = await api.get(url);
if (response.ok) {
- const {characters, segments} = response.data;
- const graph = makeGraph(characters, segments, nodeProps, edgeColor);
setPlay(response.data);
- setGraph(graph);
} else if (response.status === 404) {
setError(new Error('not found'));
} else {
@@ -72,6 +58,27 @@ const PlayInfo = ({corpusId, playId}) => {
fetchPlay();
}, [corpusId, playId]);
+ useEffect(() => {
+ async function fetchCharacters() {
+ setError(null);
+ const url = `/corpora/${corpusId}/plays/${playId}/characters`;
+ try {
+ const response = await api.get(url);
+ if (response.ok) {
+ setCharacters(response.data);
+ } else if (response.status === 404) {
+ setError(new Error('not found'));
+ } else {
+ setError(response.originalError);
+ }
+ } catch (error) {
+ console.error(error);
+ }
+ }
+
+ fetchCharacters();
+ }, [corpusId, playId]);
+
if (error && error.message === 'not found') {
return
No such play!
;
}
@@ -81,16 +88,11 @@ const PlayInfo = ({corpusId, playId}) => {
return Error!
;
}
- if (!play) {
+ if (!play || !characters) {
return Loading...
;
}
- if (!graph) {
- return No Graph!
;
- }
-
console.log('PLAY', play);
- console.log('GRAPH', graph);
const groups = play.characters
.filter((m) => Boolean(m.isGroup))
@@ -109,7 +111,7 @@ const PlayInfo = ({corpusId, playId}) => {
let tabContent = null;
let description = null;
- let characters = null;
+ let cast = null;
let metrics = null;
let segments = null;
@@ -143,8 +145,8 @@ const PlayInfo = ({corpusId, playId}) => {
);
segments = ;
} else if (tab === 'relations') {
- tabContent = ;
- characters = castList;
+ tabContent = ;
+ cast = castList;
description = (
This tab visualises kinship and other relationship data, following the
@@ -156,8 +158,8 @@ const PlayInfo = ({corpusId, playId}) => {
);
} else {
- tabContent = ;
- characters = castList;
+ tabContent = ;
+ cast = castList;
metrics = playMetrics;
description = (
@@ -184,7 +186,7 @@ const PlayInfo = ({corpusId, playId}) => {
{
- const nodes = play.characters.map((c) => ({
- id: c.id,
- label: c.name || `#${c.id}`,
- }));
- const edges = (play.relations || []).map((r, i) => ({
- id: i,
- source: r.source,
- target: r.target,
- label: r.type,
- color: edgeColors[r.type] || edgeColor,
- type: r.directed ? 'curvedArrow' : 'curve',
- }));
- const graph = {nodes, edges};
-
- const settings = {
- maxEdgeSize: 5,
- defaultLabelSize: 14,
- defaultEdgeColor: edgeColor, // FIXME: this does not seem to work
- defaultNodeColor: nodeColor,
- edgeLabelColor: 'edge',
- labelThreshold: 3,
- labelSize: 'fixed',
- drawLabels: true,
- drawEdges: true,
- drawEdgeLabels: true,
- edgeLabelSize: 'proportional',
- minNodeSize: 2,
- minArrowSize: 10,
- };
-
- const layoutOptions = {
- iterationsPerRender: 40,
- edgeWeightInfluence: 0,
- timeout: 2000,
- adjustSizes: false,
- gravity: 3,
- slowDown: 5,
- linLogMode: true,
- outboundAttractionDistribution: false,
- strongGravityMode: true,
- };
-
- const layout = ;
-
- let sigma = null;
- if (graph && graph.nodes.length > 0) {
- sigma = (
-
-
-
-
- {layout}
-
-
-
- );
- }
-
- return sigma;
-};
-
-RelationsGraph.propTypes = {
- play: PropTypes.shape({
- characters: PropTypes.array.isRequired,
- relations: PropTypes.array.isRequired,
- }).isRequired,
- nodeColor: PropTypes.string.isRequired,
- edgeColor: PropTypes.string.isRequired,
-};
-
-export default RelationsGraph;
diff --git a/src/network.js b/src/network.js
index 319d07b5..759c7d61 100644
--- a/src/network.js
+++ b/src/network.js
@@ -1,5 +1,34 @@
// network graph utility functions
+/* eslint-disable camelcase */
+const edgeColors = {
+ parent_of: '#6f42c1', // purple
+ lover_of: '#f93e3e', // red
+ related_with: '#fca130', // orange
+ associated_with: '#61affe', // blue
+ siblings: '#49cc90', // green
+ spouses: '#e83e8c', // pink
+ friends: '#1F2448', // navy
+};
+/* eslint-enable camelcase */
+
+const nodeProps = (node) => {
+ const {gender} = node;
+ const color =
+ gender === 'MALE' || gender === 'FEMALE' ? '#1f2448' : '#61affe';
+ const type = gender === 'MALE' ? 'square' : 'circle';
+ return {color, type};
+};
+
+function interpolateNodeSize(minWords, maxWords, numOfWords) {
+ const MAX_SIZE = 30;
+ const MIN_SIZE = 15;
+ return (
+ MIN_SIZE +
+ ((numOfWords - minWords) / (maxWords - minWords)) * (MAX_SIZE - MIN_SIZE)
+ );
+}
+
function getCooccurrences(segments) {
const map = {};
segments.forEach((s) => {
@@ -35,30 +64,47 @@ function getCooccurrences(segments) {
return cooccurrences;
}
-export function makeGraph(
- characters,
- segments,
- nodeProps = {},
- edgeColor = 'black'
-) {
+export function makeGraph(characters, play, type = 'cooccurence') {
+ const edgeColor = '#61affe65';
+ const maxWords = Math.max(...characters.map((c) => c.numOfWords));
+ const minWords = Math.min(...characters.map((c) => c.numOfWords));
const nodes = [];
- characters.forEach((p) => {
- const props = typeof nodeProps === 'function' ? nodeProps(p) : nodeProps;
- const node = {id: p.id, label: p.name || `#${p.id}`, ...props};
+ characters.forEach((c) => {
+ const props = nodeProps(c);
+ const nodeSize = interpolateNodeSize(minWords, maxWords, c.numOfWords);
+ const node = {
+ id: c.id,
+ label: c.name,
+ size: nodeSize || `#${c.id}`,
+ ...props,
+ };
nodes.push(node);
});
- const cooccurrences = getCooccurrences(segments);
- const edges = [];
- cooccurrences.forEach((cooc) => {
- edges.push({
- id: cooc[0] + '|' + cooc[1],
- source: cooc[0],
- target: cooc[1],
- size: cooc[2],
- // NB: we set the edge color here since the defaultEdgeColor in Sigma
- // settings does not to have any effect
- color: edgeColor,
+
+ let edges = [];
+ if (type === 'cooccurence') {
+ const cooccurrences = getCooccurrences(play.segments);
+ cooccurrences.forEach((cooc) => {
+ edges.push({
+ id: cooc[0] + '|' + cooc[1],
+ source: cooc[0],
+ target: cooc[1],
+ size: cooc[2],
+ // NB: we set the edge color here since the defaultEdgeColor in Sigma
+ // settings does not to have any effect
+ color: edgeColor,
+ });
});
- });
+ } else if (type === 'relation') {
+ edges = (play.relations || []).map((r, i) => ({
+ id: i,
+ source: r.source,
+ target: r.target,
+ label: r.type,
+ color: edgeColors[r.type] || edgeColor,
+ type: r.directed ? 'curvedArrow' : 'curve',
+ }));
+ }
+
return {nodes, edges};
}