clusters.js 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. import axios from 'axios';
  2. import * as d3 from 'd3';
  3. import ForceGraph2D from 'react-force-graph-2d';
  4. import React, {useEffect, useRef, useState} from 'react';
  5. function Clusters({stateCode}) {
  6. const [fetched, setFetched] = useState(false);
  7. const [stateRawData, setStateRawData] = useState([]);
  8. const [networkData, setNetworkData] = useState([]);
  9. function prepareNetworkData(data) {
  10. // Parse data
  11. let contractedStr = data.reduce(
  12. (acc, v) => acc + v.contractedfromwhichpatientsuspected + ', ',
  13. ''
  14. );
  15. contractedStr = contractedStr.replace(/\s+/g, '');
  16. const sources = new Set(contractedStr.match(/[^,]+/g));
  17. // Prepare nodes and links
  18. const nodes = [];
  19. const nodesSet = new Set();
  20. const links = [];
  21. data.forEach((d) => {
  22. const contractedStr = d.contractedfromwhichpatientsuspected.replace(
  23. /\s+/g,
  24. ''
  25. );
  26. const contracted = contractedStr.match(/[^,]+/g);
  27. if (contracted) {
  28. const pid = 'P' + d.patientnumber;
  29. nodesSet.add(pid);
  30. nodes.push({
  31. id: pid,
  32. group: sources.has(pid) ? 'source' : 'target',
  33. raw: d,
  34. });
  35. contracted.forEach((p) => {
  36. links.push({
  37. source: p,
  38. target: pid,
  39. });
  40. });
  41. }
  42. });
  43. // Add missing nodes
  44. links.forEach((d) => {
  45. if (!nodesSet.has(d.source)) {
  46. nodes.push({
  47. id: d.source,
  48. group: 'source',
  49. raw: d.source,
  50. });
  51. nodesSet.add(d.source);
  52. }
  53. });
  54. return {
  55. nodes: nodes,
  56. links: links,
  57. };
  58. }
  59. useEffect(() => {
  60. async function getData() {
  61. try {
  62. const rawDataResponse = await axios.get(
  63. 'https://api.covid19india.org/raw_data.json'
  64. );
  65. setStateRawData(
  66. rawDataResponse.data.raw_data.filter((d) => d.statecode === stateCode)
  67. );
  68. setFetched(true);
  69. } catch (err) {
  70. console.log(err);
  71. }
  72. }
  73. if (!fetched) {
  74. getData();
  75. }
  76. }, [fetched, stateCode]);
  77. useEffect(() => {
  78. setNetworkData(prepareNetworkData(stateRawData));
  79. }, [stateRawData]);
  80. const NetworkGraph = () => {
  81. const fgRef = useRef();
  82. useEffect(() => {
  83. const fg = fgRef.current;
  84. const width = document.getElementById('clusters').offsetWidth;
  85. const height = width;
  86. // Deactivate existing forces
  87. fg.d3Force('charge').strength(-60);
  88. fg.d3Force('collision', d3.forceCollide());
  89. fg.d3Force('x', d3.forceX().strength(0.3));
  90. fg.d3Force('y', d3.forceY().strength(0.3));
  91. fg.d3Force('box', boxForce);
  92. // Custom force to keep everything inside box
  93. function boxForce() {
  94. for (let i = 0, n = networkData.nodes.length; i < n; ++i) {
  95. const currNode = networkData.nodes[i];
  96. currNode.x = Math.max(-width, Math.min(width, currNode.x));
  97. currNode.y = Math.max(-height, Math.min(height, currNode.y));
  98. }
  99. }
  100. }, []);
  101. const width = document.getElementById('clusters').offsetWidth;
  102. const height = width;
  103. return (
  104. <ForceGraph2D
  105. ref={fgRef}
  106. width={width}
  107. height={height}
  108. graphData={networkData}
  109. nodeLabel="id"
  110. nodeColor={(node) => (node.group === 'source' ? '#dc3545' : 'gray')}
  111. linkDirectionalParticleColor={() => 'red'}
  112. linkDirectionalParticles={1}
  113. linkDirectionalParticleWidth={(link) =>
  114. link.source.id[0] === 'P' ? 2 : 0
  115. }
  116. enableZoomPanInteraction={false}
  117. />
  118. );
  119. };
  120. return <div id="clusters">{fetched ? <NetworkGraph /> : ''}</div>;
  121. }
  122. export default Clusters;