CoolFace
Apppublic

bunkalab/Argilla-Prompt-Exploration

sourceHugging Faceupdated 3y agoView on Hugging Face
5likes
Map_original.jsx335 linesDownload Raw Back to src
1import { Backdrop, CircularProgress, Button, Box } from "@mui/material";2import HelpIcon from '@mui/icons-material/Help';3import Tooltip, { tooltipClasses } from '@mui/material/Tooltip';4import Typography from '@mui/material/Typography';5import RepeatIcon from '@mui/icons-material/Repeat';6import { styled } from '@mui/material/styles';7 8import * as d3 from "d3";9import * as d3Contour from "d3-contour";10import React, { useContext, useEffect, useRef, useState } from "react";11 12import TextContainer, { topicsSizeFraction } from "./TextContainer";13import { TopicsContext } from "./UploadFileContext";14import QueryView from "./QueryView";15 16const bunkaDocs = "bunka_docs.json";17const bunkaTopics = "bunka_topics.json";18const { REACT_APP_API_ENDPOINT } = process.env;19 20/**21 * Generic tooltip22 */23export const HtmlTooltip = styled(({ className, ...props }) => (24  <Tooltip {...props} classes={{ popper: className }} />25))(({ theme }) => ({26  [`& .${tooltipClasses.popper}`]: {27    backgroundColor: '#fff',28    color: 'rgba(0, 0, 0, 0.87)',29    maxWidth: 220,30    fontSize: theme.typography.pxToRem(12),31  },32}));33 34function MapView() {35  const [selectedDocument, setSelectedDocument] = useState(null);36  const [mapLoading, setMapLoading] = useState(false);37  const [topicsCentroids, setTopicsCentroids] = useState([])38 39  const { data: apiData, isLoading: isFileProcessing } = useContext(TopicsContext);40 41  const svgRef = useRef(null);42  const scatterPlotContainerRef = useRef(null);43  const createScatterPlot = (data) => {44    const margin = {45      top: 20,46      right: 20,47      bottom: 50,48      left: 50,49    };50    const plotWidth = window.innerWidth * 0.6;51    const plotHeight = window.innerHeight - document.getElementById("top-banner").clientHeight - 50; // Adjust the height as desired52 53    d3.select(svgRef.current).selectAll("*").remove();54 55    const svg = d356      .select(svgRef.current)57      .attr("width", "100%")58      .attr("height", plotHeight);59    /**60    * SVG canvas group on which transforms apply.61    */62    const g = svg.append("g")63      .classed("canvas", true)64      .attr("transform", `translate(${margin.left}, ${margin.top})`);65    /**66    * TODO Zoom.67    */68    const zoom = d3.zoom()69      .scaleExtent([1, 3])70      .translateExtent([[0, 0], [1000, 1000]])71      .on("zoom", function ({ transform }) {72        g.attr(73          "transform",74          `translate(${transform.x ?? 0}, ${transform.y ?? 0}) scale(${transform.k ?? 1})`75        )76        //positionLabels()77        // props.setTransform?.({78        //   x: transform.x,79        //   y: transform.y,80        //   k: transform.k81        // })82      });83    svg.call(zoom);84 85    /**86     * Initial zoom.87     */88    // const defaultTransform = { k: 1 };89    // const initialTransform = defaultTransform?.k != null90    //   ? new ZoomTransform(91    //     defaultTransform.k ?? 1,92    //     defaultTransform.x ?? 0,93    //     defaultTransform.y ?? 094    //   )95    //   : d3.zoomIdentity;96    // svg.call(zoom.transform, initialTransform);97 98    const xMin = d3.min(data, (d) => d.x);99    const xMax = d3.max(data, (d) => d.x);100    const yMin = d3.min(data, (d) => d.y);101    const yMax = d3.max(data, (d) => d.y);102 103    const xScale = d3104      .scaleLinear()105      .domain([xMin, xMax]) // Use the full range of your data106      .range([0, plotWidth]);107 108    const yScale = d3109      .scaleLinear()110      .domain([yMin, yMax]) // Use the full range of your data111      .range([plotHeight, 0]);112 113    // Add contours114    const contourData = d3Contour115      .contourDensity()116      .x((d) => xScale(d.x))117      .y((d) => yScale(d.y))118      .size([plotWidth, plotHeight])119      .bandwidth(30)(120        // Adjust the bandwidth as needed121        data,122      );123 124    // Define a custom color for the contour lines125 126    const contourLineColor = "rgb(94, 163, 252)";127 128    // Append the contour path to the SVG with a custom color129    g130      .selectAll("path.contour")131      .data(contourData)132      .enter()133      .append("path")134      .attr("class", "contour")135      .attr("d", d3.geoPath())136      .style("fill", "none")137      .style("stroke", contourLineColor) // Set the contour line color to the custom color138      .style("stroke-width", 1);139 140    /*141    const circles = svg.selectAll('circle')142    .data(data)143    .enter()144    .append('circle')145    .attr('cx', (d) => xScale(d.x))146    .attr('cy', (d) => yScale(d.y))147    .attr('r', 5)148    .style('fill', 'lightblue')149    .on('click', (event, d) => {150        // Show the content and topic name of the clicked point in the text container151        setSelectedDocument(d);152        // Change the color to pink on click153        circles.style('fill', (pointData) => (pointData === d) ? 'pink' : 'lightblue');154    });155    */156 157    const centroids = data.filter((d) => d.x_centroid && d.y_centroid);158    setTopicsCentroids(centroids);159 160    g161      .selectAll("circle.topic-centroid")162      .data(centroids)163      .enter()164      .append("circle")165      .attr("class", "topic-centroid")166      .attr("cx", (d) => xScale(d.x_centroid))167      .attr("cy", (d) => yScale(d.y_centroid))168      .attr("r", 8) // Adjust the radius as needed169      .style("fill", "red") // Adjust the fill color as needed170      .style("stroke", "black")171      .style("stroke-width", 2)172      .on("click", (event, d) => {173        // Show the content and topic name of the clicked topic centroid in the text container174        setSelectedDocument(d);175      });176 177    // Add text labels for topic names178    g179      .selectAll("text.topic-label")180      .data(centroids)181      .enter()182      .append("text")183      .attr("class", "topic-label")184      .attr("x", (d) => xScale(d.x_centroid))185      .attr("y", (d) => yScale(d.y_centroid) - 12) // Adjust the vertical position186      .text((d) => d.name) // Use the 'name' property for topic names187      .style("text-anchor", "middle"); // Center-align the text188 189    const convexHullData = data.filter((d) => d.convex_hull);190 191    for (const d of convexHullData) {192      const hull = d.convex_hull;193      const hullPoints = hull.x_coordinates.map((x, i) => [xScale(x), yScale(hull.y_coordinates[i])]);194 195      g196        .append("path")197        .datum(d3.polygonHull(hullPoints))198        .attr("class", "convex-hull-polygon")199        .attr("d", (d1) => `M${d1.join("L")}Z`)200        .style("fill", "none")201        .style("stroke", "rgba(255, 255, 255, 0.5)") // White with 50% transparency202        .style("stroke-width", 2);203    }204 205    // Add polygons for topics. Delete if no clicking on polygons206    const topicsPolygons = g207      .selectAll("polygon.topic-polygon")208      .data(centroids)209      .enter()210      .append("polygon")211      .attr("class", "topic-polygon")212      .attr("points", (d) => {213        const hull = d.convex_hull;214        const hullPoints = hull.x_coordinates.map((x, i) => [xScale(x), yScale(hull.y_coordinates[i])]);215        return hullPoints.map((point) => point.join(",")).join(" ");216      })217      .style("fill", "transparent")218      .style("stroke", "transparent")219      .style("stroke-width", 2); // Adjust the border width as needed220 221    let currentlyClickedPolygon = null;222 223    topicsPolygons.on("click", (event, d) => {224      // Reset the fill color of the previously clicked polygon to transparent light grey225      if (currentlyClickedPolygon !== null) {226        currentlyClickedPolygon.style("fill", "transparent");227        currentlyClickedPolygon.style("stroke", "transparent");228      }229 230      // Set the fill color of the clicked polygon to transparent light grey and add a red border231      const clickedPolygon = d3.select(event.target);232      clickedPolygon.style("fill", "rgba(200, 200, 200, 0.4)");233      clickedPolygon.style("stroke", "red");234 235      currentlyClickedPolygon = clickedPolygon;236 237      // Display the topic name and content from top_doc_content with a scroll system238      if (d.top_doc_content) {239        // Render the TextContainer component with topic details240        setSelectedDocument(d);241      }242    });243  };244 245  useEffect(() => {246    if (REACT_APP_API_ENDPOINT === "local" || apiData === undefined) {247      setMapLoading(true);248      // Fetch the JSON data locally249      fetch(`/${bunkaDocs}`)250        .then((response) => response.json())251        .then((localData) => {252          // Fetch the local topics data and merge it with the existing data253          fetch(`/${bunkaTopics}`)254            .then((response) => response.json())255            .then((topicsData) => {256              // Merge the topics data with the existing data257              const mergedData = localData.concat(topicsData);258 259              // Call the function to create the scatter plot after data is loaded260              createScatterPlot(mergedData);261            })262            .catch((error) => {263              console.error("Error fetching topics data:", error);264            })265            .finally(() => {266              setMapLoading(false);267            });268        })269        .catch((error) => {270          console.error("Error fetching JSON data:", error);271        })272        .finally(() => {273          setMapLoading(false);274        });275    } else {276      // Call the function to create the scatter plot with the data provided by TopicsContext277      createScatterPlot(apiData.docs.concat(apiData.topics));278    }279 280    // After the data is loaded, set the default topic281    if (apiData && apiData.topics && apiData.topics.length > 0) {282      // Set the default topic to the first topic in the list283      setSelectedDocument(apiData.topics[0]);284    }285  }, [apiData]);286 287 288  const mapDescription = "This map is created by embedding documents in a two-dimensional space. Two documents are close to each other if they share similar semantic features, such as vocabulary, expressions, and language. The documents are not directly represented on the map; instead, they are grouped into clusters. A cluster is a set of documents that share similarities. A cluster  is automatically described by a few words that best describes it.";289 290  return (291    <div className="json-display">292      {(isFileProcessing || mapLoading) ? (293        <Backdrop open={isFileProcessing || mapLoading} style={{ zIndex: 9999 }}>294          <CircularProgress color="primary" />295        </Backdrop>296      ) : (297        <div className="scatter-plot-and-text-container">298          <div className="scatter-plot-container" ref={scatterPlotContainerRef}>299            <HtmlTooltip300              title={301                <React.Fragment>302                  <Typography color="inherit">{mapDescription}</Typography>303                </React.Fragment>304              }305              followCursor306            >307              <HelpIcon style={{308                position: "relative",309                top: 10,310                left: 40,311                border: "none"312              }} />313            </HtmlTooltip>314            <svg ref={svgRef} />315          </div>316          <div className="text-container" >317            {selectedDocument !== null ? (318              <>319                {/* <Box sx={{ marginBottom: "1em" }}>320                  <Button sx={{ width: "100%" }} component="label" variant="outlined" startIcon={<RepeatIcon />} onClick={() => setSelectedDocument(null)}>321                    Upload another CSV file322                  </Button>323                </Box> */}324                <TextContainer topicName={selectedDocument.name} topicSizeFraction={topicsSizeFraction(topicsCentroids, selectedDocument.size)} content={selectedDocument.top_doc_content} />325              </>326            ) : <QueryView />}327          </div>328        </div>329      )}330    </div>331  );332}333 334export default MapView;335