M. Heeb
December 27, 2023
chart3 = {
  const links = data.links.map(d => Object.create(d));
  const nodes = data.nodes.map(d => Object.create(d));
  
  
  // apply fixed positions found in localStorage
  const fx = JSON.parse(localStorage.getItem("fixedNodes"));
  if (fx && fx.length > 0)
    for (const f of fx) {
      const i = f.i,
        fx = f.fx,
        fy = f.fy;
      if (i && nodes[i] && fx && fy) {
        nodes[i].fx = fx;
        nodes[i].fy = fy;
      }
    }
  //create forceSimulation instance
  const simulation = d3
    .forceSimulation(nodes)
    .force("link", d3.forceLink(links).id(d => d.id).strength(d => d.strength))
    .force("charge", d3.forceManyBody().strength(-300))
    .force('center', d3.forceCenter(width /2, height /2))
    .force("x", d3.forceX())
    .force("y", d3.forceY());
    
    
    //.force("link", d3.forceLink(links).id(d => d.id))
    //.force("charge", d3.forceManyBody())
  //selecting a svg element with D3
  const svg = d3.create("svg")
      .attr("viewBox", [0, 0, width, height]);
      
  
  const link = svg
    .append("g")
    .attr("stroke", "#999")
    .attr("stroke-opacity", 0.6)
    .selectAll("line")
    .data(links)
    .join("line")
    .attr("stroke-width", d => d.strength)
    .attr("stroke", "blue");
    const textElements = svg
    .append('g')
    .selectAll('text')
    .data(nodes)
    .enter().append('text')
    .text(node => node.label)
    .attr('font-size', 15)
    .attr('dx', 15)
    .attr('dy', 4)
    
    const node = svg
    .append("g")
    .attr("stroke", "#fff")
    .attr("stroke-width", 1.5)
    .selectAll("circle")
    .data(nodes)
    .join("circle")
    .attr("r", d => d.radius)
    .attr("fill", color)
    
    
    .call(drag(simulation));
    svg
    .append("text")
    .text("Title: This is a graph")
    .attr("stroke", "red")
    .attr("x", 20)
    .attr("y", 20);
    
  node.append("title").text(d => d.id);
  //start the simulation and define a tick functions that is executed on every simulation tick
  simulation.on("tick", () => {
    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);
    textElements
      .attr("x", d => d.x)
      .attr("y", d => d.y)
    node
      .attr("cx", d => d.x)
      .attr("cy", d => d.y)
      .attr("stroke", d => (d.fx ? "#333" : "#fff"))
      
    
  });
  invalidation.then(() => simulation.stop());
  return svg.node();
}drag = simulation => {
  function dragstarted(d) {
    if (!d3.event.active) simulation.alphaTarget(0.3).restart();
    if (!d.fx) {
      d.fx = d.x;
      d.fy = d.y;
    } else {
      d.fx = d.fy = null;
    }
  }
  function dragged(d) {
    d.fx = d3.event.x;
    d.fy = d3.event.y;
  }
  function dragended(d) {
    if (!d3.event.active) simulation.alphaTarget(0);
    // save fixed locations to localStorage
    localStorage.setItem(
      "fixedNodes",
      JSON.stringify(
        simulation
          .nodes()
          .map((d, i) => ({ i, fx: d.fx, fy: d.fy }))
          .filter(d => d.fx)
      )
    );
  }
  return d3
    .drag()
    .on("start", dragstarted)
    .on("drag", dragged)
    .on("end", dragended);
}viewof data2 = Inputs.button([
  ["create new node data", () => data1.nodes.push(JSON.parse(JSON.stringify(noobj)))]
  ], {label: "create the new node data"})noobj:
viewof data3 = Inputs.button([
  ["create new link data", () => data1.links.push(JSON.parse(JSON.stringify(liobj)))]
  ], {label: "create the new link data"})liobj:
data1 (gelesen aus File C:-doc-projects-d3-graphen-html-quarto-graph-daten3.json):
data (internes Arbeitsarray für Visualisierung):
sites = await d3.json(
  "https://neocities.org/api/info?sitename=youpi", {mode: "cors"}
)
//infos = sites.map(site => {
//  const info = site.info;
//  return {
//    sitename: info.sitename,
//    hits: info.hits,
//    created_at: info.created_at,
//    last_updated: info.last_updated,
//    domain: info.domain,
//    tags: info.tags
//  }
//})View the infos of the site:
contributors = await d3.json(
  "https://api.github.com/repos/pandas-dev/pandas/stats/contributors"
)
commits = contributors.map(contributor => {
  const author = contributor.author;
  return {
    name: author.login,
    title: author.login,
    group: author.type,
    value: contributor.total
  }
})View the data sorted by number of commits:
---
title: "The Graph Editor/Creator (working with tabs)"
author: "M. Heeb"
date: 12.27.2023
format:
  html:
    toc: false
    echo: false
    keep-hidden: true
    code-tools: true
    page-layout: custom
---
```{ojs}
d3 = require("d3@5")    //mit d3@7 funktioniert der Graph nicht
topojson = require("topojson")
```
```{ojs}
data1 = FileAttachment("d3-graph-daten3.json").json();
```
```{ojs}
height = 800
```
```{ojs}
Width = 600
```
::: {.panel-tabset}
## graph
```{ojs}
chart3 = {
  const links = data.links.map(d => Object.create(d));
  const nodes = data.nodes.map(d => Object.create(d));
  
  
  // apply fixed positions found in localStorage
  const fx = JSON.parse(localStorage.getItem("fixedNodes"));
  if (fx && fx.length > 0)
    for (const f of fx) {
      const i = f.i,
        fx = f.fx,
        fy = f.fy;
      if (i && nodes[i] && fx && fy) {
        nodes[i].fx = fx;
        nodes[i].fy = fy;
      }
    }
  //create forceSimulation instance
  const simulation = d3
    .forceSimulation(nodes)
    .force("link", d3.forceLink(links).id(d => d.id).strength(d => d.strength))
    .force("charge", d3.forceManyBody().strength(-300))
    .force('center', d3.forceCenter(width /2, height /2))
    .force("x", d3.forceX())
    .force("y", d3.forceY());
    
    
    //.force("link", d3.forceLink(links).id(d => d.id))
    //.force("charge", d3.forceManyBody())
  //selecting a svg element with D3
  const svg = d3.create("svg")
      .attr("viewBox", [0, 0, width, height]);
      
  
  const link = svg
    .append("g")
    .attr("stroke", "#999")
    .attr("stroke-opacity", 0.6)
    .selectAll("line")
    .data(links)
    .join("line")
    .attr("stroke-width", d => d.strength)
    .attr("stroke", "blue");
    const textElements = svg
    .append('g')
    .selectAll('text')
    .data(nodes)
    .enter().append('text')
    .text(node => node.label)
    .attr('font-size', 15)
    .attr('dx', 15)
    .attr('dy', 4)
    
    const node = svg
    .append("g")
    .attr("stroke", "#fff")
    .attr("stroke-width", 1.5)
    .selectAll("circle")
    .data(nodes)
    .join("circle")
    .attr("r", d => d.radius)
    .attr("fill", color)
    
    
    .call(drag(simulation));
    svg
    .append("text")
    .text("Title: This is a graph")
    .attr("stroke", "red")
    .attr("x", 20)
    .attr("y", 20);
    
  node.append("title").text(d => d.id);
  //start the simulation and define a tick functions that is executed on every simulation tick
  simulation.on("tick", () => {
    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);
    textElements
      .attr("x", d => d.x)
      .attr("y", d => d.y)
    node
      .attr("cx", d => d.x)
      .attr("cy", d => d.y)
      .attr("stroke", d => (d.fx ? "#333" : "#fff"))
      
    
  });
  invalidation.then(() => simulation.stop());
  return svg.node();
}
```
```{ojs}
color = {
  const scale = d3.scaleOrdinal(d3.schemeCategory10);
  return d => scale(d.group);
}
```
```{ojs}
drag = simulation => {
  function dragstarted(d) {
    if (!d3.event.active) simulation.alphaTarget(0.3).restart();
    if (!d.fx) {
      d.fx = d.x;
      d.fy = d.y;
    } else {
      d.fx = d.fy = null;
    }
  }
  function dragged(d) {
    d.fx = d3.event.x;
    d.fy = d3.event.y;
  }
  function dragended(d) {
    if (!d3.event.active) simulation.alphaTarget(0);
    // save fixed locations to localStorage
    localStorage.setItem(
      "fixedNodes",
      JSON.stringify(
        simulation
          .nodes()
          .map((d, i) => ({ i, fx: d.fx, fy: d.fy }))
          .filter(d => d.fx)
      )
    );
  }
  return d3
    .drag()
    .on("start", dragstarted)
    .on("drag", dragged)
    .on("end", dragended);
}
```
## create new Node data:
```{ojs}
  noobj = new Object();  //node-obj
```
```{ojs}
//| panel: input
viewof id = Inputs.text({placeholder: "z.B. a",label: "node id: ", value: "z"})
```
```{ojs}
   noobj.id = id;
```
```{ojs}
//| panel: input
viewof group = Inputs.text({placeholder: "z.B. p",label: "group: ", value: "p"})
```
```{ojs}
  noobj.group  = group;
```
```{ojs}
//| panel: input
viewof label = Inputs.text({placeholder: "z.B. hallo",label: "label: ", value: "z"})
```
```{ojs}
  noobj.label = label;
```
```{ojs}
//| panel: input
viewof radius = Inputs.text({placeholder: "z.B. 5",label: "radius: ", value: 5})
```
```{ojs}
  noobj.radius = radius;
```
```{ojs}
//| panel: input
viewof data2 = Inputs.button([
  ["create new node data", () => data1.nodes.push(JSON.parse(JSON.stringify(noobj)))]
  ], {label: "create the new node data"})
```
noobj:
```{ojs}
 noobj
```
## create new Link data:
```{ojs}
 liobj = new Object();  //link-obj
 var1 = data.nodes.map(d => d.id)    // oder d.label)
```
```{ojs}
//| panel: input
viewof source = Inputs.select(var1, {label: "from Source node (id): "})
```
```{ojs}
  liobj.source = source;
```
```{ojs}
//| panel: input
viewof target = Inputs.select(var1, {label: "to Target node (id)"})
```
```{ojs}
  liobj.target  = target;
```
```{ojs}
//| panel: input
viewof strength = Inputs.text({placeholder: "z.B. 1",label: "strenth: ", value: 1})
```
```{ojs}
  liobj.strength = strength;
```
```{ojs}
//| panel: input
viewof data3 = Inputs.button([
  ["create new link data", () => data1.links.push(JSON.parse(JSON.stringify(liobj)))]
  ], {label: "create the new link data"})
```
liobj:
```{ojs}
 liobj
```
## add created data to graph
```{ojs}
//| panel: input
viewof data = Inputs.button([
  ["Add node- or link-data to the graph", () => data1]
], {value: data1})
```
## data view
data1 (gelesen aus File C:\Users\micha\OneDrive\Documents\R-doc-projects\hemi-d3-graphen-html-quarto\d3-graph-daten3.json):
```{ojs}
data1
```
data (internes Arbeitsarray für Visualisierung):
```{ojs}
data
```
## copy graph data to Clipboard:
```{ojs}
Inputs.button("copy graph data to clipboard", {value: null, reduce: () => navigator.clipboard.writeText(JSON.stringify(data))})
```
## neocity:
```{ojs}
sites = await d3.json(
  "https://neocities.org/api/info?sitename=youpi", {mode: "cors"}
)
//infos = sites.map(site => {
//  const info = site.info;
//  return {
//    sitename: info.sitename,
//    hits: info.hits,
//    created_at: info.created_at,
//    last_updated: info.last_updated,
//    domain: info.domain,
//    tags: info.tags
//  }
//})
```
View the infos of the site:
```{ojs}
sites
//Inputs.table(infos, { sort: "sitename", reverse: true })
```
## github REST API:
```{ojs}
contributors = await d3.json(
  "https://api.github.com/repos/pandas-dev/pandas/stats/contributors"
)
commits = contributors.map(contributor => {
  const author = contributor.author;
  return {
    name: author.login,
    title: author.login,
    group: author.type,
    value: contributor.total
  }
})
```
View the data sorted by number of commits:
```{ojs}
Inputs.table(commits, { sort: "value", reverse: true })
```
:::