首页 > 解决方案 > 将 CSV 解析为一个数组后,在 D3 力图中组合节点和链接(Angular 和 D3.JS)

问题描述

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

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

我使用的 CSV Parser 库是 NgxCsvParser。

客户端解析器组件

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.组件

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

}

标签: javascriptnode.jsangulartypescriptd3.js

解决方案


由于NgxCsvParser内部使用rxjsObservables,您可以使用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);
 }

推荐阅读