[Angular] D3.js Line Chart 만들기

dh·2023년 2월 27일
0
post-thumbnail

저번에 연결한 MSW에 요청을 보내고 받은 데이터로 D3.js 라이브러리를 이용해 선그래프를 만들고 그래프를 갱신하는 기능을 만들거임.

ng g c test로 라인그래프를 만들 test컴포넌트를 생성한다.

test.component.html

<h2>timeSeries!</h2>
<div id="timeSeries" [attr.width]="width" [attr.height]="height" >
</div>

app.component.html

app 컴포넌트에 test컴포넌트를 넣고 라디오버튼으로 데이터를 선택해서 그래프를 그릴 예정이다.
[(ngModel)]="startTime"로 value를 test.component.ts의 start변수에 전달해주고 ngOnChanges(){}안에 코드를 넣어 라디오버튼을 선택해 start값이 변경되면 그에 맞는 그래프를 그려주게 할 예정이다.

<div>
    <input type="radio" id="10min" value="600000" [(ngModel)]="startTime" name="selector" checked>
    <label for="10min">10분</label>
    <input type="radio" id="30min" value="1800000" [(ngModel)]="startTime" name="selector" >
    <label for="30min">30분</label>
    <input type="radio" id="hour" value="3600000" [(ngModel)]="startTime" name="selector" >
    <label for="hour">1시간</label>
</div>

<app-test [start]="startTime" ></app-test>
<app-pie [start]="startTime" ></app-pie>
<app-value [start]="startTime"></app-value>

1. svg 생성

timeSeries Div 안에 svg태그를 붙이고 크기를 설정한다.
그리고 svg에 g태그(요소를 그룹화함)를 붙이고 위치를 조정한다.

test.component.ts

 this.svg = d3.select('#timeSeries')
          .append("svg")
          .attr("width", this.width+this.margin.left+this.margin.right)
           .attr("height", this.height+this.margin.top+this.margin.bottom)
          .append('g')
          .attr('transform', 'translate(' + -1+ ',' + this.margin.top + ')');

2. x축, y축 생성

x축, y축의 값을 설정한다.
x축은 시작시간과 끝시간을 domain으로 설정하고 range의 영역안에 domain 스케일을 맞춘다.
range와 translate는 직접 축을 생성해가며 +,-를 조절하여 위치를 잡았다.
y축의 범위도 0~가장큰값으로 설정한다.
x축은 아래쪽, y축은 왼쪽으로 설정하고 ticks는 눈금의 개수임
위에서 생성한 svg에 x,y축을 생성한다.

test.component.ts

let xScale = d3.scaleTime().domain([new Date(this.data[0].x), new Date(this.data[msg.times.length-1].x)]).range([80, this.width]); 
let yScale = d3.scaleLinear().domain([0, max]).range([300, 30]);

const xAxis = d3.axisBottom(xScale).ticks(12)
const yAxis = d3.axisLeft(yScale).ticks(10);

const xAxisSVG = this.svg.append("g").attr("class","xAxis").attr("transform", "translate(0,"+330+ ")")
                    .style("font-size","15px").attr("stroke-width","2")
                    .call(xAxis);
const yAxisSVG = this.svg.append("g").attr("class","yAxis").attr("transform", "translate(80,0)")
                    .style("font-size","15px").attr("stroke-width","2")
                    .call(yAxis)
                    .call((g:any) => g.select(".domain").remove())
                    .call((g:any) => g.selectAll(".tick line").clone()
                          .attr("x2", this.width)
                          .attr("stroke-opacity", 0.1))

x,y축 생성 결과

3. 선 그리기

이제 svg에 선을 그릴 차례
데이터 형식을 [{x: 1677512170000,y:8006603}, ....] 처럼 hash형식으로 만들었음. 따라서 x축에는 x값, y축에는 y값을 매핑시킨다.
그리고 svg에 path태그를 붙여 선을 그려준다.

test.component.ts

 let linearGenerator = d3.line()
            .curve(d3.curveLinear)
            .x((d:any)=>xScale(new Date(d.x)))
            .y((d:any)=>yScale(d.y))
       
 this.svg
   .append("path")
   .attr("class", "line")            
   .attr("d", linearGenerator(this.data))  
   .attr("fill", "none")              
   .attr("stroke-width", 1)            
   .attr("stroke", "steelblue")   

결과

10분 전부터 현재시간까지 데이터

30분 전부터 현재시간까지 데이터

1시간 전부터 현재시간까지 데이터

전체코드

test.component.ts

import { Component, Input, OnInit, OnChanges } from '@angular/core';
import { DataService } from '../data.service';
import * as d3 from 'd3';

@Component({
  selector: 'app-test',
  templateUrl: './test.component.html',
  styleUrls: ['./test.component.css']
})
export class TestComponent implements OnInit, OnChanges   {

  
  @Input() start = '';
  margin = {top: 10, right: 30, bottom: 30, left: 50}
  width =1000
  height=350;
  data : any[]=[]
  svg: any

  constructor(private dataService: DataService){
  }
  ngOnChanges(){
   d3.select("#timeSeries").selectChildren().remove()
    this.drawchart(+this.start)
  }
  
  
  drawchart = (time:Number) =>{
      var now = Date.now()
      this.dataService.getData(now- +time, now,"timeseries").subscribe(
        msg=>{
          this.data=[]
          let max=0
          for(let i=0; i<msg.data.length; i++){
            if(msg.data[i] > max){
              max=msg.data[i]
            }
            this.data.push({
              x:msg.times[i],
              y:msg.data[i]
            })
          }
      
          this.svg = d3.select('#timeSeries')
          .append("svg")
          .attr("width", this.width+this.margin.left+this.margin.right)
           .attr("height", this.height+this.margin.top+this.margin.bottom)
          .append('g')
          .attr('transform', 'translate(' + -1+ ',' + this.margin.top + ')');
        
          let xScale = d3.scaleTime().domain([new Date(this.data[0].x), new Date(this.data[msg.times.length-1].x)]).range([80, this.width]); 
          let yScale = d3.scaleLinear().domain([0, max]).range([300, 30]);
       
          const xAxis = d3.axisBottom(xScale).ticks(12)
          const yAxis = d3.axisLeft(yScale).ticks(10);
       
          const xAxisSVG = this.svg.append("g").attr("class","xAxis").attr("transform", "translate(0,"+330+ ")")
                    .style("font-size","15px").attr("stroke-width","2")
                    .call(xAxis);
          const yAxisSVG = this.svg.append("g").attr("class","yAxis").attr("transform", "translate(80,0)")
                          .style("font-size","15px").attr("stroke-width","2")
                          .call(yAxis)
                          .call((g:any) => g.select(".domain").remove())
                          .call((g:any) => g.selectAll(".tick line").clone()
                              .attr("x2", this.width)
                              .attr("stroke-opacity", 0.1))
                     
       
           let linearGenerator = d3.line()
            .curve(d3.curveLinear)
            .x((d:any)=>xScale(new Date(d.x)))
            .y((d:any)=>yScale(d.y))
       
            this.svg
              .append("path")
              .attr("class", "line")            
              .attr("d", linearGenerator(this.data))  
              .attr("fill", "none")              
              .attr("stroke-width", 1)            
              .attr("stroke", "steelblue")   
                      
            });
  	}
}

  

0개의 댓글