【问题标题】:Combine Nodes & Links in D3 Force Graph After CSV is Parsed to make one Array (Angular & D3.JS)在解析 CSV 以创建一个数组后,在 D3 力图中合并节点和链接(Angular 和 D3.JS)
【发布时间】:2021-06-27 01:05:42
【问题描述】:

我正在开发一个应用程序,我在 Angular 和 D3.JS 的客户端解析 CSV 文件以创建图形节点

如何组合两个 .csv 文件数组来创建一个数据集?加载 CSV 文件后,将数组返回到 this.files。

我使用的 CSV Parser 库是 NgxCsvParser。

client-parser.component

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { NgxCsvParser } from 'ngx-csv-parser';
import { NgxCSVParserError } from 'ngx-csv-parser';


@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.scss']
})

export class LoginComponent implements OnInit {
  files: File[] = [];

  connections: any[] = [];
  header = true;
  nodes: any[] = [];
  links: any[] = [];


  constructor(
    public router: Router,
    private ngxCsvParser: NgxCsvParser
  ) {}

  ngOnInit(): void {}


  onSelect(event: {
    addedFiles: any;
  }) {
    this.files.push(...event.addedFiles);

    this.ngxCsvParser.parse(this.files[0], {
        header: this.header,
        delimiter: ','
      })
      .pipe().subscribe((result: Array < any > ) => {

        this.connections = result;

        this.nodes.push({
          "firstName": "You",
          "lastName": "",
          "name": "You",
          "company": ""
        });

        for (const source of this.connections) {
          const sourceName = source["First Name"] + " " + source["Last Name"];

          this.links.push({
            "source": "You",
            "target": sourceName
          });

          this.nodes.push({
            "firstName": source["First Name"],
            "lastName": source["Last Name"],
            "name": sourceName,
            "company": source["Company"]
          });

          for (const target of this.connections) {
            const targetName = target["First Name"] + " " + target["Last Name"];
            if (sourceName != targetName && source["Company"] == target["Company"]) {
              this.links.push({
                "source": sourceName,
                "target": targetName
              });
            }
          }

          const graph = {
            "nodes": this.nodes,
            "links": this.links
          }

          console.log(graph);

          localStorage.setItem('graph', JSON.stringify(graph));

        }

      }, (error: NgxCSVParserError) => {
        console.log('Error', error);
      });

  //  this.router.navigateByUrl('/graph');

  }

  onRemove(event: File) {
    console.log(event);
    this.files.splice(this.files.indexOf(event), 1);
  }

}

d3.component

import { Component, OnInit } from '@angular/core';
import * as d3 from 'd3';
import {Node} from '../d3/models/node';
import {Link} from '../d3/models/link';


@Component({
  selector: 'app-graph',
  templateUrl: './graph.component.html',
  styleUrls: ['./graph.component.scss']
})
export class GraphComponent implements OnInit {

  constructor() { }

  ngOnInit() {
    const retrievedObject = localStorage.getItem('graph');
    const graph = JSON.parse(retrievedObject);
    this.loadForceDirectedGraph(graph.nodes, graph.links);

  }

  loadForceDirectedGraph(nodes: Node[], links: Link[]) {
    const svg = d3.select('svg');
    const width = +svg.attr('width');
    const height = +svg.attr('height');

    const color = d3.scaleOrdinal(d3.schemeBlues[9]);


    const simulation = d3.forceSimulation()
      .force('link', d3.forceLink().id((d: Node) => d.name))// the id of the node
      .force("charge", d3.forceManyBody().strength(-5).distanceMax(0.5 * Math.min(width, height)))
      .force('center', d3.forceCenter(width / 2, height / 2));

    console.log(nodes, links);

    const link = svg.append('g')
      .attr('class', 'links')
      .selectAll('line')
      .data(links)
      .enter()
      .append('line')
      .attr('stroke-width', d => Math.sqrt(d.index))
      .attr('stroke', 'black');

    const node = svg.append('g')
      .attr('class', 'nodes')
      .selectAll('circle')
      .data(nodes)
      .enter()
      .append('circle')
      .attr('r', 8)
      .attr("fill", function(d) { return color(d.company); })
      .call(d3.drag()
        .on('start', dragStarted)
        .on('drag', dragged)
        .on('end', dragEnded)
      );

     node.append('text')
            .text((d) => d.company)
            .attr('x', 6)
            .attr('y', 3);


    node.append('title').text((d) => d.name);

    simulation
      .nodes(nodes)
      .on('tick', ticked);

    simulation.force<d3.ForceLink<any, any>>('link')
      .links(links);

    function ticked() {
      node
        .attr('cx', d => d.x)
        .attr('cy', d => d.y);

      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);
    }

    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;
    }
  }

}

【问题讨论】:

  • 限制节点数和链接数是什么意思?你只是想在某个数字后停下来吗?您想将相似的节点/链接组合在一起吗?如果您只显示部分数据,该图表可能不准确。
  • 基本上如何将两个 .csv 文件(例如 this.files[i] )组合在一起以创建一个图形
  • 您读取一个 CSV 并创建一个数组以传递给 D3,对吗?为什么不能将数据从第二个 CSV 推送到同一个数组?我错过了什么?
  • @adamdport 很好,一旦数组数据被传递,它会自动定向到另一个将显示图表的组件。如何确保仅在添加第二个 CSV 后才显示图表。
  • @kjamp 在这种情况下,@Maxie 已经发布的答案似乎是可用的。他们的onResultHandler 只会在两个 CSV 都被解析后触发,并且您将可以访问两者的数据来构建您的数组。

标签: javascript node.js angular typescript d3.js


【解决方案1】:

由于NgxCsvParser 内部使用rxjs Observables,您可以使用forkJoin 运算符来组合来自多个 Observables 的结果。

 import { forkJoin } from "rxjs";

 onSelect(event: { addedFiles: any }) {
    this.files.push(...event.addedFiles);

    const fileOne = this.ngxCsvParser.parse(this.files[0], {
      header: this.header,
      delimiter: ","
    });

    const fileTwo = this.ngxCsvParser.parse(this.files[1], {
      header: this.header,
      delimiter: ","
    });

    forkJoin(fileOne, fileTwo).subscribe(onResultHandler, onErrorHandler);
 }

【讨论】:

    猜你喜欢
    • 2023-03-20
    • 2017-05-17
    • 1970-01-01
    • 1970-01-01
    • 2013-05-18
    • 2017-11-12
    • 2018-02-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多