这是用户在 2024-7-2 21:13 为 https://stargazing.observablehq.cloud/visualization-of-painting-path/Code 保存的双语快照页面,由 沉浸式翻译 提供双语支持。了解如何保存?
交易师徒流传师徒友人友人师徒父子友人友人题跋题跋流传同乡流传流传印章交易协助协助交易印章传承父子父子印章印章父子父子友人父子救画救画继父子父子流传流传交易传承交易交易交易题跋题跋交易协助交易协助传承题跋题跋题跋题跋题跋题跋题跋合作流传流传流传交易捐赠题跋题跋题跋题跋题跋题跋题跋题跋题跋题跋合作共同持有黄公望无用禅师郑樗全真道士金志扬沈周姚绶文徵明李景清樊舜举谈思言文彭王穉登安邵芳周炳文华仲亨董其昌吴正志吴洪亮吴洪昌吴贞度吴洪裕邹之麟吴贞吉吴贞明吴贞观张范我季寓庸季八士高士奇王鸿绪沈德潜詹氏安歧傅恒乾隆金士松梁诗正吴其贞王廷宾曹友卿吴湖帆韩葑王同愈沈尹默叶恭绰夏敬观冯超然张珩吴诗初浙江省博物馆台北故宫博物院合璧展出黄公望无用禅师郑樗全真道士金志扬沈周姚绶文徵明李景清樊舜举谈思言文彭王穉登安邵芳周炳文华仲亨董其昌吴正志吴洪亮吴洪昌吴贞度吴洪裕邹之麟吴贞吉吴贞明吴贞观张范我季寓庸季八士高士奇王鸿绪沈德潜詹氏安歧傅恒乾隆金士松梁诗正吴其贞王廷宾曹友卿吴湖帆韩葑王同愈沈尹默叶恭绰夏敬观冯超然张珩吴诗初浙江省博物馆台北故宫博物院合璧展出
function force1(data){
  const width =1200;
  const height = 1500;
  const r = 15 ;
  const w = 1;
  const customColors = [  
    "#69b3a2", 
    "#f05b4f", 
    "#ffa500", 
    "#50e3c2", 
    "#0099cc", 
    "#99cc99", 
    "#e6e6e6", 
    "#66b3ff", 
    "#ff99cc", 
    "#ccff99"  
  ];  
  
  const color = d3.scaleOrdinal(customColors);  

  const links = data.links.map((d) => Object.create(d));
  const nodes = data.nodes.map((d) => Object.create(d));

  
  const simulation = d3.forceSimulation(nodes)
    .force("link", d3.forceLink(links).id((d) => d.id).distance(80))
    .force("charge", d3.forceManyBody().strength(-300))
    .force("center", d3.forceCenter(width / 2, height / 2))
    .on("tick", ticked)

  const svg = d3.create("svg")
    .attr("height", height)
    .attr("width", width)
    .attr("viewBox", [0, 0, width, height])
    // .attr("style", "width: 800px; height: 600px; top:-657px; left:-321px; position: absolute;");

  const link = svg.append("g")
    .attr("stroke", "var(--theme-foreground-faint)")
    .attr("stroke-width",w)
    .selectAll("line")
    .data(links)
    .join("line")
    .attr("stroke-dasharray", function(d) {  
    if (d.value === "流传"||d.value === "题跋"||d.value ==="印章") {  
            return "10,10"; 
        } else {  
            return "0"; // 实线样式  
        }})  
      .attr("stroke-width", function(d) {  
      if (d.value === "流传"||d.value === "交易"||d.value ==="捐赠"||d.value ==="传承"||d.value ==="合作") {  
            return 2*w; 
     } else {  
            return w; // 实线样式  
     }})  
        
    
  
  const linkText = svg.append("g").selectAll("text")
    .data(links)
    .enter().append("text")
    .attr("x", d => (d.source.x + d.target.x) / 2)  
    .attr("y", d => (d.source.y + d.target.y) / 2)
    .text(d => d.value)  
    .attr("text-anchor", "middle")
    .attr("alignment-baseline", "middle")
    .attr("font-size", "25")
    .attr("fill", "transparent")
    .style("user-select", "none")
  
    svg.on("mousemove", function(event) {
    const [mouseX, mouseY] = d3.pointer(event);

    // 检查鼠标是否靠近连接线
    linkText.attr("fill", d => isNearLink(d, mouseX, mouseY) ? "black" : "transparent");
    // nodeText.attr("fill", d => isNearLink(d, mouseX, mouseY) ? "white" : "transparent");
    });


  const node = svg.append("g")
      .attr("stroke", "var(--theme-background)")
      .attr("stroke-width", 0)
    .selectAll("circle")
    .data(nodes)
    .join("circle")
      .attr("r", r)
      .attr("fill", (d) => color(d.group))
      .call(drag(simulation));

  const nodeText = svg.append("g").selectAll("text")
    .data(nodes)
    .enter().append("text")
    .attr("x", d => d.x)
    .attr("y", d => d.y)
    .text(d => d.id)
    .attr("text-anchor", "middle")
    .attr("alignment-baseline", "middle")
    .attr("fill", "transparent")
    .attr("pointer-events", "none") 
    .style("user-select", "none"); 

  node.append("title")
      .text((d) => d.id);
  
  nodes.forEach((d, i) => {
  d.x = i * (width / nodes.length);
  d.y = height / 2;
});
  
  function drag(simulation) {

    function dragstarted(event) {
      if (!event.active) simulation.alphaTarget(0.3).restart();
      event.subject.fx = event.subject.x;
      event.subject.fy = event.subject.y;
    }

    function dragged(event) {
      event.subject.fx = event.x;
      event.subject.fy = event.y;
    }

    function dragended(event) {
      if (!event.active) simulation.alphaTarget(0);
      event.subject.fx = null;
      event.subject.fy = null;
    }

    return d3.drag()
        .on("start", dragstarted)
        .on("drag", dragged)
        .on("end", dragended);
  }
  function dragged(event) {
     const xMin = 0;
     const xMax = width; // 限制在整个宽度范围内
     const yMin = 0;
     const yMax = height; // 限制在整个高度范围内
 
     event.subject.fx = Math.max(xMin, Math.min(xMax, event.x));
     event.subject.fy = Math.max(yMin, Math.min(yMax, event.y));
  }
  function ticked() {
   link
       .attr("x1", (d) => d.source.x)
       .attr("y1", (d) => d.source.y)
       .attr("x2", (d) => d.target.x)
       .attr("y2", (d) => d.target.y);

   node
       .attr("cx", (d) => d.x)
       .attr("cy", (d) => d.y);

   linkText
       .attr("x", d => (d.source.x + d.target.x) / 2)
       .attr("y", d => (d.source.y + d.target.y) / 2);
    
    nodeText
       .attr("x", d => d.x)
       .attr("y", d => d.y);
  }

  function isNearLink(link, mouseX, mouseY) {
    const x1 = link.source.x;
    const y1 = link.source.y;
    const x2 = link.target.x;
    const y2 = link.target.y;
  
    const distance = Math.abs((y2-y1)*mouseX - (x2-x1)*mouseY + x2*y1 - y2*x1) / Math.sqrt((y2-y1)**2 + (x2-x1)**2);
  
    return distance < 5; // 根据需要调整触发距离
  }


  return svg.node();
}

1000km1000km50050000富春山居图子明卷黄潜临本沈周临本
function Graph(data){
  const width = 620;
  const height = width;
  const innerRadius = 130;
  const outerRadius = 200;
  const FCS = data.FCS.map((d) => Object.create(d));
  const WYS = data.WYS.map((d) => Object.create(d));
  const SST = data.SST.map((d) => Object.create(d));
  const HJT = data.HJT.map((d) => Object.create(d));
  const ZMJ = data.ZMJ.map((d) => Object.create(d));
  const SZJ = data.SZJ.map((d) => Object.create(d));

  const x = d3.scaleLinear()
    .domain([1347, 2024])
    .range([0, 2 * Math.PI]);

  const y = d3.scalePow()
    .exponent(0.8)
    .domain([0, 1000])
    .range([innerRadius, outerRadius]);

  const lineGenerator = d3.lineRadial()
    .curve(d3.curveCatmullRom)
    .angle(d => x(d.year))
    .radius(d => y(d.avg));


  const lines = [
    {data: WYS, color: "steelblue"}, 
    {data: ZMJ, color: "yellow"}, 
    {data: HJT, color: "red"}, 
    {data: FCS, color: "steelblue"}, 
    {data: SST, color: "steelblue"}, 
    {data: SZJ, color: "green"},
    ];

  const svg = d3.create("svg")
    .attr("width", width)
    .attr("height", height)
    .attr("viewBox", [-width / 2, -height / 2, width, height])
    .attr("style", "width: 100%; height: auto; font: 10px sans-serif;")
    .attr("stroke-linejoin", "round")
    .attr("stroke-linecap", "round");

  const tooltip = d3.select("body")
    .append("div")
    .attr("class", "tooltip")
    .style("position", "absolute")
    .style("background", "#f9f9f9")
    .style("padding", "5px")
    .style("border", "1px solid #d3d3d3")
    .style("border-radius", "5px")
    .style("pointer-events", "none")
    .style("opacity", 0);

  const defs = svg.append("defs");

  lines.forEach((line, index) => {
    const gradientId = `gradient-${index}`;
    
    const gradient = defs.append("radialGradient")
      .attr("id", gradientId)
      .attr("cx", "50%")
      .attr("cy", "50%")
      .attr("r", "100%")
      .attr("fx", "50%")
      .attr("fy", "50%");
    
    gradient.append("stop")
      .attr("offset", "35%")
      .attr("stop-color", "#f9f0ea")
      .attr("stop-opacity", 0.7);

    gradient.append("stop")
      .attr("offset", "90%")
      .attr("stop-color", line.color)
      .attr("stop-opacity", 0.1);

    svg.append("path")
      .datum(line.data)
      .attr("fill", `url(#${gradientId})`)
      .attr("stroke", line.color)
      .attr("stroke-width", 1)
      .attr("stroke-opacity", 0.5)
      .attr("d", lineGenerator);

    svg.append("path")
      .datum(line.data)
      .attr("fill", "none")
      .attr("stroke", "transparent")
      .attr("stroke-width", 15)
      .attr("d", lineGenerator)
  .on("mouseover", function(event, d) {
    tooltip.transition()
      .duration(100)
      .style("opacity", 1);
  })


      .on("mousemove", (event, d) => {
        const [xPos, yPos] = d3.pointer(event);
        const angle = Math.atan2(yPos, xPos);
        const year = Math.round(x.invert(angle));
        const dataPoint = d.find(data => data.year === year);
        const avgValue = dataPoint ? dataPoint.avg : null;
        tooltip.html(`Year: ${year}`)
          .style("left", (event.pageX + 10) + "px")
          .style("top", (event.pageY - 28) + "px")
      })
      
      .on("mouseout", (d) => {
        tooltip.transition()
        .duration(100)
        .style("opacity", 0)
      });
  });



  svg.append("g")
    .attr("text-anchor", "middle")
    .attr("font-size","5")
    .selectAll()
    .data(y.ticks(2).reverse())
    .join("g")
    .call(g => g.append("circle")
      .attr("fill", "none")
      .attr("stroke", "currentColor")
      .attr("stroke-opacity", 0.05)
      .attr("r", y))
    .call(g => g.append("text")
      .attr("y", d => -y(d))
      .text((x, i) => `${x.toFixed(0)}${i ? "" : "km"}`)
      .clone(true)
      .attr("y", d => y(d)))
      .style("user-select","none")
      .attr("z-index",100)

  svg.append("g")
    .selectAll()
    .data(x.ticks(7))
    .join("g")
      .call(g => g.append("path")
          .attr("stroke", "#000")
          .attr("stroke-opacity", 0.05)
          .attr("d", d => `
            M${d3.pointRadial(x(d), innerRadius + 100)}
            L${d3.pointRadial(x(d), outerRadius - 70)}
          `))

svg.append("circle")  
    .attr("cx", 0)   
    .attr("cy", 0)   
    .attr("r", 80) 
    .style("fill", "#f9f0ea"); 
    // .style("z-index", "10"); 

var rects = [  
    { x: 270, y: 80, width: 20, height:  8, color: "blue", text: "富春山居图" },  
    { x: 240, y: 120, width: 20, height: 8, color: "yellow", text: "子明卷" },  
    { x: 210, y: 160, width: 20, height: 8, color: "red", text: "黄潜临本" },  
    { x: 180, y: 200, width: 20, height: 8, color: "green", text: "沈周临本" }  
];  
  
rects.forEach(function(rect) {  
    // 添加矩形  
    svg.append("rect")  
        .attr("x", rect.x)  
        .attr("y", rect.y)  
        .attr("width", rect.width)  
        .attr("height", rect.height)  
        .style("fill", rect.color);  
  
    // 添加文本  
    svg.append("text")  
        .attr("x", rect.x + rect.width / 2) // 文本居中  
        .attr("y", rect.y + rect.height + 10) // 文本在矩形下方  
        .attr("text-anchor", "middle") // 文本居中  
        .attr("font-size","6")
        .text(rect.text);  
});


return svg.node()
}