{"version":3,"sources":["canvas-utils.ts","use-debounce.ts","views/GraphSettingsController.tsx","views/GraphEventsController.tsx","views/GraphDataController.tsx","views/Panel.tsx","views/DescriptionPanel.tsx","views/ClustersPanel.tsx","views/SearchField.tsx","views/GraphTitle.tsx","views/Root.tsx","index.tsx"],"names":["drawLabel","context","data","settings","label","size","labelSize","font","labelFont","weight","labelWeight","width","measureText","fillStyle","fillRect","x","y","fillText","useDebounce","value","delay","useState","debouncedValue","setDebouncedValue","useEffect","handler","setTimeout","clearTimeout","GraphSettingsController","children","hoveredNode","sigma","useSigma","graph","getGraph","debouncedHoveredNode","setSetting","descLabel_1_Size","descLabel_2_Size","descLabel_3_Size","descLabel_4_Size","descLabel_1_","desc_1","descLabel_2_","desc_2","descLabel_3_","desc_3","descLabel_4_","desc_4","clusterLabel","desc","beginPath","shadowOffsetX","shadowOffsetY","shadowBlur","shadowColor","labelWidth","descLabel_1_Width","descLabel_2_Width","descLabel_3_Width","descLabel_4_Width","clusterLabelWidth","textWidth","Math","max","round","w","hLabel","hdescLabel_1_","hdescLabel_2_","hdescLabel_3_","hdescLabel_4_","ctx","height","radius","moveTo","lineTo","quadraticCurveTo","closePath","drawRoundRect","fill","color","drawHover","getNodeDisplayData","key","hoveredColor","node","hasEdge","zIndex","image","highlighted","edge","hasExtremity","hidden","getMouseLayer","document","querySelector","GraphEventsController","setHoveredNode","registerEvents","useRegisterEvents","clickNode","getNodeAttribute","window","open","enterNode","mouseLayer","classList","add","leaveNode","remove","GraphDataController","dataset","filters","clusters","keyBy","tags","nodes","forEach","addNode","omit","cluster","process","tag","edges","source","target","addEdge","scores","map","minDegree","min","maxDegree","forEachNode","setNodeAttribute","clear","Panel","title","initiallyDeployed","isDeployed","setIsDeployed","dom","useRef","current","parentElement","scrollTo","top","offsetTop","behavior","className","ref","type","onClick","v","duration","DescriptionPanel","href","ClustersPanel","toggleCluster","setClusters","nodesPerCluster","useMemo","index","_","maxNodesPerCluster","values","visibleClustersCount","Object","keys","length","visibleNodesPerCluster","setVisibleNodesPerCluster","requestAnimationFrame","sortedClusters","sortBy","mapValues","nodesCount","visibleNodesCount","checked","onChange","id","htmlFor","style","background","borderColor","SearchField","search","setSearch","setValues","selected","setSelected","refreshValues","newValues","lcSearch","toLowerCase","attributes","indexOf","push","nodeDisplayData","getCamera","animate","ratio","placeholder","list","e","searchString","valueItem","find","onKeyPress","prettyPercentage","val","toFixed","GraphTitle","visibleItems","setVisibleItems","forEachEdge","_2","_3","_4","order","Root","showContents","setShowContents","dataReady","setDataReady","setDataset","filtersState","setFiltersState","fetch","then","res","json","constant","graphOptions","initialSettings","nodeProgramClasses","getNodeProgramImage","labelRenderer","defaultNodeType","defaultEdgeType","labelDensity","labelGridCellSize","labelRenderedSizeThreshold","customEnterFullScreen","customExitFullScreen","customZoomIn","customZoomOut","customZoomCenter","ReactDOM","render","StrictMode","getElementById"],"mappings":"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","file":"static/js/main.36916b51.chunk.js","sourcesContent":["import { NodeDisplayData, PartialButFor, PlainObject } from \"sigma/types\";\nimport { Settings } from \"sigma/settings\";\n\nconst TEXT_COLOR = \"#000000\";\n\n/**\n * This function draw in the input canvas 2D context a rectangle.\n * It only deals with tracing the path, and does not fill or stroke.\n */\nexport function drawRoundRect(\n ctx: CanvasRenderingContext2D,\n x: number,\n y: number,\n width: number,\n height: number,\n radius: number,\n): void {\n ctx.beginPath();\n ctx.moveTo(x + radius, y);\n ctx.lineTo(x + width - radius, y);\n ctx.quadraticCurveTo(x + width, y, x + width, y + radius);\n ctx.lineTo(x + width, y + height - radius);\n ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);\n ctx.lineTo(x + radius, y + height);\n ctx.quadraticCurveTo(x, y + height, x, y + height - radius);\n ctx.lineTo(x, y + radius);\n ctx.quadraticCurveTo(x, y, x + radius, y);\n ctx.closePath();\n}\n\n/**\n * Custom hover renderer\n */\nexport function drawHover(context: CanvasRenderingContext2D, data: PlainObject, settings: PlainObject) {\n const size = settings.labelSize;\n const font = settings.labelFont;\n const weight = settings.labelWeight;\n const descLabel_1_Size = size - 2;\n const descLabel_2_Size = size - 2;\n const descLabel_3_Size = size - 2;\n const descLabel_4_Size = size - 2;\n\n\n const label = data.label;\n const descLabel_1_ = data.desc_1;\n const descLabel_2_ = data.desc_2;\n const descLabel_3_ = data.desc_3;\n const descLabel_4_ = data.desc_4;\n const clusterLabel = data.desc;\n\n // Then we draw the label background\n context.beginPath();\n context.fillStyle = \"#fff\";\n context.shadowOffsetX = 0;\n context.shadowOffsetY = 2;\n context.shadowBlur = 8;\n context.shadowColor = \"#000\";\n\n context.font = `${weight} ${size}px ${font}`;\n const labelWidth = context.measureText(label).width;\n \n context.font = `${weight} ${descLabel_1_Size}px ${font}`;\n const descLabel_1_Width = descLabel_1_ ? context.measureText(descLabel_1_).width : 0;\n context.font = `${weight} ${descLabel_1_Size}px ${font}`;\n \n context.font = `${weight} ${descLabel_2_Size}px ${font}`;\n const descLabel_2_Width = descLabel_2_ ? context.measureText(descLabel_2_).width : 0;\n context.font = `${weight} ${descLabel_2_Size}px ${font}`;\n \n context.font = `${weight} ${descLabel_3_Size}px ${font}`;\n const descLabel_3_Width = descLabel_3_ ? context.measureText(descLabel_3_).width : 0;\n context.font = `${weight} ${descLabel_3_Size}px ${font}`;\n \n context.font = `${weight} ${descLabel_4_Size}px ${font}`;\n const descLabel_4_Width = descLabel_4_ ? context.measureText(descLabel_4_).width : 0;\n context.font = `${weight} ${descLabel_4_Size}px ${font}`;\n\n const clusterLabelWidth = clusterLabel ? context.measureText(clusterLabel).width : 0;\n\n const textWidth = Math.max(labelWidth, descLabel_1_Width, descLabel_2_Width, descLabel_3_Width, descLabel_4_Width, clusterLabelWidth);\n\n const x = Math.round(data.x);\n const y = Math.round(data.y);\n const w = Math.round(textWidth + size / 2 + data.size + 3);\n const hLabel = Math.round(size / 2 + 4);\n const hdescLabel_1_ = descLabel_1_ ? Math.round(descLabel_1_Size / 2 + 9) : 0;\n const hdescLabel_2_ = descLabel_2_ ? Math.round(descLabel_2_Size / 2 + 9) : 0;\n const hdescLabel_3_ = descLabel_3_ ? Math.round(descLabel_3_Size / 2 + 9) : 0;\n const hdescLabel_4_ = descLabel_4_ ? Math.round(descLabel_4_Size / 2 + 9) : 0;\n const hClusterLabel = Math.round(descLabel_1_Size / 2 + 9);\n\n drawRoundRect(context, x, y - 12, w, hLabel + hdescLabel_1_ + hdescLabel_2_ + hdescLabel_3_ + hdescLabel_4_ + 20, 5);\n context.closePath();\n context.fill();\n\n context.shadowOffsetX = 0;\n context.shadowOffsetY = 0;\n context.shadowBlur = 0;\n\n // And finally we draw the labels\n // MAIN LABEL\n context.fillStyle = TEXT_COLOR;\n context.font = `${weight} ${size}px ${font}`;\n context.fillText(label, data.x + data.size + 3, data.y + hLabel - 3);\n \n // DESCRIPTION\n if (descLabel_1_) {\n context.fillStyle = data.color;\n context.font = `${weight} ${descLabel_1_Size}px ${font}`;\n context.fillText(descLabel_1_, data.x + data.size + 3, data.y + hLabel + hdescLabel_1_);\n }\n \n if (descLabel_2_) {\n context.fillStyle = data.color;\n context.font = `${weight} ${descLabel_2_Size}px ${font}`;\n context.fillText(descLabel_2_, data.x + data.size + 3, data.y + hLabel + hdescLabel_1_ + hdescLabel_2_);\n }\n \n if (descLabel_3_) {\n context.fillStyle = data.color;\n context.font = `${weight} ${descLabel_3_Size}px ${font}`;\n context.fillText(descLabel_3_, data.x + data.size + 3, data.y + hLabel + hdescLabel_1_ + hdescLabel_2_ + hdescLabel_3_);\n }\n\n if (descLabel_4_) {\n context.fillStyle = data.color;\n context.font = `${weight} ${descLabel_4_Size}px ${font}`;\n context.fillText(descLabel_4_, data.x + data.size + 3, data.y + hLabel + hdescLabel_1_ + hdescLabel_2_ + hdescLabel_3_ + hdescLabel_4_);\n }\n\n}\n\n/**\n * Custom label renderer\n */\nexport default function drawLabel(\n context: CanvasRenderingContext2D,\n data: PartialButFor,\n settings: Settings,\n): void {\n if (!data.label) return;\n\n const size = settings.labelSize,\n font = settings.labelFont,\n weight = settings.labelWeight;\n\n context.font = `${weight} ${size}px ${font}`;\n const width = context.measureText(data.label).width + 8;\n\n context.fillStyle = \"#ffffffcc\";\n context.fillRect(data.x + data.size, data.y + size / 3 - 15, width, 20);\n\n context.fillStyle = \"#000\";\n context.fillText(data.label, data.x + data.size + 3, data.y + size / 3);\n}\n","import { useEffect, useState } from \"react\";\n\nfunction useDebounce(value: T, delay: number): T {\n // State and setters for debounced value\n const [debouncedValue, setDebouncedValue] = useState(value);\n\n useEffect(\n () => {\n // Update debounced value after delay\n const handler = setTimeout(() => {\n if (value !== debouncedValue) setDebouncedValue(value);\n }, delay);\n\n // Cancel the timeout if value changes (also on delay change or unmount)\n // This is how we prevent debounced value from updating if value is changed ...\n // .. within the delay period. Timeout gets cleared and restarted.\n return () => {\n clearTimeout(handler);\n };\n },\n [value, delay], // Only re-call effect if value or delay changes\n );\n\n return debouncedValue;\n}\n\nexport default useDebounce;\n","import { useSigma } from \"react-sigma-v2\";\nimport { FC, useEffect } from \"react\";\n\nimport { drawHover } from \"../canvas-utils\";\nimport useDebounce from \"../use-debounce\";\n\nconst NODE_FADE_COLOR = \"#bbb\";\nconst EDGE_FADE_COLOR = \"#eee\";\n\nconst GraphSettingsController: FC<{ hoveredNode: string | null }> = ({ children, hoveredNode }) => {\n const sigma = useSigma();\n const graph = sigma.getGraph();\n\n // Here we debounce the value to avoid having too much highlights refresh when\n // moving the mouse over the graph:\n const debouncedHoveredNode = useDebounce(hoveredNode, 40);\n\n /**\n * Initialize here settings that require to know the graph and/or the sigma\n * instance:\n */\n useEffect(() => {\n sigma.setSetting(\"hoverRenderer\", (context, data, settings) =>\n drawHover(context, { ...sigma.getNodeDisplayData(data.key), ...data }, settings),\n );\n }, [sigma, graph]);\n\n /**\n * Update node and edge reducers when a node is hovered, to highlight its\n * neighborhood:\n */\n useEffect(() => {\n const hoveredColor: string = debouncedHoveredNode ? sigma.getNodeDisplayData(debouncedHoveredNode)!.color : \"\";\n\n sigma.setSetting(\n \"nodeReducer\",\n debouncedHoveredNode\n ? (node, data) =>\n node === debouncedHoveredNode ||\n graph.hasEdge(node, debouncedHoveredNode) ||\n graph.hasEdge(debouncedHoveredNode, node)\n ? { ...data, zIndex: 1 }\n : { ...data, zIndex: 0, label: \"\", color: NODE_FADE_COLOR, image: null, highlighted: false }\n : null,\n );\n sigma.setSetting(\n \"edgeReducer\",\n debouncedHoveredNode\n ? (edge, data) =>\n graph.hasExtremity(edge, debouncedHoveredNode)\n ? { ...data, color: hoveredColor, size: 2 }\n : { ...data, color: EDGE_FADE_COLOR, hidden: true }\n : null,\n );\n }, [debouncedHoveredNode]);\n\n return <>{children};\n};\n\nexport default GraphSettingsController;\n","import { useRegisterEvents, useSigma } from \"react-sigma-v2\";\nimport { FC, useEffect } from \"react\";\n\nfunction getMouseLayer() {\n return document.querySelector(\".sigma-mouse\");\n}\n\nconst GraphEventsController: FC<{ setHoveredNode: (node: string | null) => void }> = ({ setHoveredNode, children }) => {\n const sigma = useSigma();\n const graph = sigma.getGraph();\n const registerEvents = useRegisterEvents();\n\n /**\n * Initialize here settings that require to know the graph and/or the sigma\n * instance:\n */\n useEffect(() => {\n registerEvents({\n clickNode({ node }) {\n if (!graph.getNodeAttribute(node, \"hidden\")) {\n window.open(graph.getNodeAttribute(node, \"URL\"), \"_blank\");\n }\n },\n enterNode({ node }) {\n setHoveredNode(node);\n // TODO: Find a better way to get the DOM mouse layer:\n const mouseLayer = getMouseLayer();\n if (mouseLayer) mouseLayer.classList.add(\"mouse-pointer\");\n },\n leaveNode() {\n setHoveredNode(null);\n // TODO: Find a better way to get the DOM mouse layer:\n const mouseLayer = getMouseLayer();\n if (mouseLayer) mouseLayer.classList.remove(\"mouse-pointer\");\n },\n });\n }, []);\n\n return <>{children};\n};\n\nexport default GraphEventsController;\n","import { useSigma } from \"react-sigma-v2\";\nimport { FC, useEffect } from \"react\";\nimport { keyBy, omit } from \"lodash\";\n\nimport { Dataset, FiltersState } from \"../types\";\n\nconst GraphDataController: FC<{ dataset: Dataset; filters: FiltersState }> = ({ dataset, filters, children }) => {\n const sigma = useSigma();\n const graph = sigma.getGraph();\n\n /**\n * Feed graphology with the new dataset:\n */\n useEffect(() => {\n if (!graph || !dataset) return;\n\n const clusters = keyBy(dataset.clusters, \"key\");\n const tags = keyBy(dataset.tags, \"key\");\n\n dataset.nodes.forEach((node) =>\n graph.addNode(node.key, {\n ...node,\n ...omit(clusters[node.cluster], \"key\"),\n image: `${process.env.PUBLIC_URL}/images/${tags[node.tag].image}`,\n }),\n );\n dataset.edges.forEach(([source, target]) => graph.addEdge(source, target, { size: 1 }));\n\n // Use degrees as node sizes:\n const scores = graph.nodes().map((node) => graph.getNodeAttribute(node, \"score\"));\n const minDegree = Math.min(...scores);\n const maxDegree = Math.max(...scores);\n const MIN_NODE_SIZE = 4;\n const MAX_NODE_SIZE = 12;\n graph.forEachNode((node) =>\n graph.setNodeAttribute(\n node,\n \"size\",\n ((graph.getNodeAttribute(node, \"score\") - minDegree) / (maxDegree - minDegree)) *\n (MAX_NODE_SIZE - MIN_NODE_SIZE) +\n MIN_NODE_SIZE,\n ),\n );\n\n return () => graph.clear();\n }, [graph, dataset]);\n\n /**\n * Apply filters to graphology:\n */\n useEffect(() => {\n const { clusters, tags } = filters;\n graph.forEachNode((node, { cluster, tag }) =>\n graph.setNodeAttribute(node, \"hidden\", !clusters[cluster] || !tags[tag]),\n );\n }, [graph, filters]);\n\n return <>{children};\n};\n\nexport default GraphDataController;\n","import React, { FC, useEffect, useRef, useState } from \"react\";\nimport { MdExpandLess, MdExpandMore } from \"react-icons/md\";\nimport AnimateHeight from \"react-animate-height\";\n\nconst DURATION = 300;\n\nconst Panel: FC<{ title: JSX.Element | string; initiallyDeployed?: boolean }> = ({\n title,\n initiallyDeployed,\n children,\n}) => {\n const [isDeployed, setIsDeployed] = useState(initiallyDeployed || false);\n const dom = useRef(null);\n\n useEffect(() => {\n if (isDeployed)\n setTimeout(() => {\n if (dom.current) dom.current.parentElement!.scrollTo({ top: dom.current.offsetTop - 5, behavior: \"smooth\" });\n }, DURATION);\n }, [isDeployed]);\n\n return (\n
\n

\n {title}{\" \"}\n \n

\n \n {children}\n \n
\n );\n};\n\nexport default Panel;\n","import React, { FC } from \"react\";\nimport { BsInfoCircle } from \"react-icons/all\";\n\nimport Panel from \"./Panel\";\n\nconst DescriptionPanel: FC = () => {\n return (\n \n Description\n \n }\n >\n

\n This map represents the community of Twitter users in connection with the topic of eDNA.\n

\n

\nA first set of accounts was collected by searching for keywords \"eDNA\" and \"metabarcoding\" in the users' biography. Then, the network was expanded by collecting all the accounts followed by at least five users of this initial set. Finally, the list was refined to keep only the users who tweeted or retweeted about eDNA or metabarcoding more than three times in their 100 last contributions.\n

\n

\nThis resulted in a very dense network of 891 accounts. Only the symetric links (accounts follow each other) are represented. The size of the nodes are related to the number of incident edges. Colors delineate sub-community clusters detected algorithmically.\n

\n \n

\nData were collected and processed with R and the javascript app is powered by sigma.js.\n

\n \n

\nCrafted by @FrancoisKeck in November 2021. Data are not automatically updated.\n

\n \n );\n};\n\nexport default DescriptionPanel;\n","import React, { FC, useEffect, useMemo, useState } from \"react\";\nimport { useSigma } from \"react-sigma-v2\";\nimport { sortBy, values, keyBy, mapValues } from \"lodash\";\n\nimport { Cluster, FiltersState } from \"../types\";\nimport { MdGroupWork } from \"react-icons/md\";\nimport Panel from \"./Panel\";\nimport { AiOutlineCheckCircle, AiOutlineCloseCircle } from \"react-icons/all\";\n\nconst ClustersPanel: FC<{\n clusters: Cluster[];\n filters: FiltersState;\n toggleCluster: (cluster: string) => void;\n setClusters: (clusters: Record) => void;\n}> = ({ clusters, filters, toggleCluster, setClusters }) => {\n const sigma = useSigma();\n const graph = sigma.getGraph();\n\n const nodesPerCluster = useMemo(() => {\n const index: Record = {};\n graph.forEachNode((_, { cluster }) => (index[cluster] = (index[cluster] || 0) + 1));\n return index;\n }, []);\n\n const maxNodesPerCluster = useMemo(() => Math.max(...values(nodesPerCluster)), [nodesPerCluster]);\n const visibleClustersCount = useMemo(() => Object.keys(filters.clusters).length, [filters]);\n\n const [visibleNodesPerCluster, setVisibleNodesPerCluster] = useState>(nodesPerCluster);\n useEffect(() => {\n // To ensure the graphology instance has up to data \"hidden\" values for\n // nodes, we wait for next frame before reindexing. This won't matter in the\n // UX, because of the visible nodes bar width transition.\n requestAnimationFrame(() => {\n const index: Record = {};\n graph.forEachNode((_, { cluster, hidden }) => !hidden && (index[cluster] = (index[cluster] || 0) + 1));\n setVisibleNodesPerCluster(index);\n });\n }, [filters]);\n\n const sortedClusters = useMemo(\n () => sortBy(clusters, (cluster) => -nodesPerCluster[cluster.key]),\n [clusters, nodesPerCluster],\n );\n\n return (\n \n Clusters\n {visibleClustersCount < clusters.length ? (\n \n {\" \"}\n ({visibleClustersCount} / {clusters.length})\n \n ) : (\n \"\"\n )}\n \n }\n >\n

\n Click a cluster to show/hide related pages from the network.\n

\n

\n {\" \"}\n \n

\n
    \n {sortedClusters.map((cluster) => {\n const nodesCount = nodesPerCluster[cluster.key];\n const visibleNodesCount = visibleNodesPerCluster[cluster.key] || 0;\n return (\n 1 ? \"s\" : \"\"}${\n visibleNodesCount !== nodesCount ? ` (only ${visibleNodesCount} visible)` : \"\"\n }`}\n >\n toggleCluster(cluster.key)}\n id={`cluster-${cluster.key}`}\n />\n \n \n );\n })}\n
\n \n );\n};\n\nexport default ClustersPanel;\n","import React, { KeyboardEvent, ChangeEvent, FC, useEffect, useState } from \"react\";\nimport { useSigma } from \"react-sigma-v2\";\nimport { Attributes } from \"graphology-types\";\nimport { BsSearch } from \"react-icons/bs\";\n\nimport { FiltersState } from \"../types\";\n\n/**\n * This component is basically a fork from React-sigma-v2's SearchControl\n * component, to get some minor adjustments:\n * 1. We need to hide hidden nodes from results\n * 2. We need custom markup\n */\nconst SearchField: FC<{ filters: FiltersState }> = ({ filters }) => {\n const sigma = useSigma();\n\n const [search, setSearch] = useState(\"\");\n const [values, setValues] = useState>([]);\n const [selected, setSelected] = useState(null);\n\n const refreshValues = () => {\n const newValues: Array<{ id: string; label: string }> = [];\n const lcSearch = search.toLowerCase();\n if (!selected && search.length > 1) {\n sigma.getGraph().forEachNode((key: string, attributes: Attributes): void => {\n if (!attributes.hidden && attributes.label && attributes.label.toLowerCase().indexOf(lcSearch) === 0)\n newValues.push({ id: key, label: attributes.label });\n });\n }\n setValues(newValues);\n };\n\n // Refresh values when search is updated:\n useEffect(() => refreshValues(), [search]);\n\n // Refresh values when filters are updated (but wait a frame first):\n useEffect(() => {\n requestAnimationFrame(refreshValues);\n }, [filters]);\n\n useEffect(() => {\n if (!selected) return;\n\n sigma.getGraph().setNodeAttribute(selected, \"highlighted\", true);\n const nodeDisplayData = sigma.getNodeDisplayData(selected);\n\n if (nodeDisplayData)\n sigma.getCamera().animate(\n { ...nodeDisplayData, ratio: 0.05 },\n {\n duration: 600,\n },\n );\n\n return () => {\n sigma.getGraph().setNodeAttribute(selected, \"highlighted\", false);\n };\n }, [selected]);\n\n const onInputChange = (e: ChangeEvent) => {\n const searchString = e.target.value;\n const valueItem = values.find((value) => value.label === searchString);\n if (valueItem) {\n setSearch(valueItem.label);\n setValues([]);\n setSelected(valueItem.id);\n } else {\n setSelected(null);\n setSearch(searchString);\n }\n };\n\n const onKeyPress = (e: KeyboardEvent) => {\n if (e.key === \"Enter\" && values.length) {\n setSearch(values[0].label);\n setSelected(values[0].id);\n }\n };\n\n return (\n
\n \n \n \n {values.map((value: { id: string; label: string }) => (\n \n ))}\n \n
\n );\n};\n\nexport default SearchField;\n","import React, { FC, useEffect, useState } from \"react\";\nimport { FiltersState } from \"../types\";\nimport { useSigma } from \"react-sigma-v2\";\n\nfunction prettyPercentage(val: number): string {\n return (val * 100).toFixed(1) + \"%\";\n}\n\nconst GraphTitle: FC<{ filters: FiltersState }> = ({ filters }) => {\n const sigma = useSigma();\n const graph = sigma.getGraph();\n\n const [visibleItems, setVisibleItems] = useState<{ nodes: number; edges: number }>({ nodes: 0, edges: 0 });\n useEffect(() => {\n // To ensure the graphology instance has up to data \"hidden\" values for\n // nodes, we wait for next frame before reindexing. This won't matter in the\n // UX, because of the visible nodes bar width transition.\n requestAnimationFrame(() => {\n const index = { nodes: 0, edges: 0 };\n graph.forEachNode((_, { hidden }) => !hidden && index.nodes++);\n graph.forEachEdge((_, _2, _3, _4, source, target) => !source.hidden && !target.hidden && index.edges++);\n setVisibleItems(index);\n });\n }, [filters]);\n\n return (\n
\n

A cartography of the eDNA community on Twitter

\n

\n \n {graph.order} account{graph.order > 1 ? \"s\" : \"\"}{\" \"}\n {visibleItems.nodes !== graph.order\n ? ` (only ${prettyPercentage(visibleItems.nodes / graph.order)} visible)`\n : \"\"}\n , {graph.size} link\n {graph.size > 1 ? \"s\" : \"\"}{\" \"}\n {visibleItems.edges !== graph.size\n ? ` (only ${prettyPercentage(visibleItems.edges / graph.size)} visible)`\n : \"\"}\n \n

\n
\n );\n};\n\nexport default GraphTitle;\n","import React, { FC, useEffect, useState } from \"react\";\nimport { SigmaContainer, ZoomControl, FullScreenControl } from \"react-sigma-v2\";\n\nimport getNodeProgramImage from \"sigma/rendering/webgl/programs/node.image\";\nimport { omit, mapValues, keyBy, constant } from \"lodash\";\n\nimport GraphSettingsController from \"./GraphSettingsController\";\nimport GraphEventsController from \"./GraphEventsController\";\nimport GraphDataController from \"./GraphDataController\";\nimport DescriptionPanel from \"./DescriptionPanel\";\nimport { Dataset, FiltersState } from \"../types\";\nimport ClustersPanel from \"./ClustersPanel\";\nimport SearchField from \"./SearchField\";\nimport drawLabel from \"../canvas-utils\";\nimport GraphTitle from \"./GraphTitle\";\nimport TagsPanel from \"./TagsPanel\";\n\nimport \"react-sigma-v2/lib/react-sigma-v2.css\";\nimport { GrClose } from \"react-icons/gr\";\nimport { BiRadioCircleMarked, BiBookContent } from \"react-icons/bi\";\nimport { BsArrowsFullscreen, BsFullscreenExit, BsZoomIn, BsZoomOut } from \"react-icons/bs\";\n\nconst Root: FC = () => {\n const [showContents, setShowContents] = useState(false);\n const [dataReady, setDataReady] = useState(false);\n const [dataset, setDataset] = useState(null);\n const [filtersState, setFiltersState] = useState({\n clusters: {},\n tags: {},\n });\n const [hoveredNode, setHoveredNode] = useState(null);\n\n // Load data on mount:\n useEffect(() => {\n fetch(`${process.env.PUBLIC_URL}/dataset.json`)\n .then((res) => res.json())\n .then((dataset: Dataset) => {\n setDataset(dataset);\n setFiltersState({\n clusters: mapValues(keyBy(dataset.clusters, \"key\"), constant(true)),\n tags: mapValues(keyBy(dataset.tags, \"key\"), constant(true)),\n });\n requestAnimationFrame(() => setDataReady(true));\n });\n }, []);\n\n if (!dataset) return null;\n\n return (\n
\n \n \n \n \n\n {dataReady && (\n <>\n
\n
\n setShowContents(true)}\n title=\"Show caption and description\"\n >\n \n \n
\n }\n customExitFullScreen={}\n />\n }\n customZoomOut={}\n customZoomCenter={}\n />\n
\n
\n
\n setShowContents(false)}\n title=\"Show caption and description\"\n >\n \n \n
\n \n
\n \n \n\n setFiltersState((filters) => ({\n ...filters,\n clusters,\n }))\n }\n toggleCluster={(cluster) => {\n setFiltersState((filters) => ({\n ...filters,\n clusters: filters.clusters[cluster]\n ? omit(filters.clusters, cluster)\n : { ...filters.clusters, [cluster]: true },\n }));\n }}\n />\n
\n
\n \n )}\n \n
\n );\n};\n\nexport default Root;\n","import React from \"react\";\nimport ReactDOM from \"react-dom\";\n\nimport \"./styles.css\";\nimport Root from \"./views/Root\";\n\nReactDOM.render(\n \n \n ,\n document.getElementById(\"root\"),\n);\n"],"sourceRoot":""}