From 189b3471e8e493dbd08668bf39c2462677eb6789 Mon Sep 17 00:00:00 2001 From: testation21 Date: Wed, 23 Feb 2022 02:36:11 +0900 Subject: [PATCH 1/4] [FEAT] add pie chart visualization with D3 --- .../research-status.component.html | 24 ++-- .../research-status.component.ts | 117 ++++++++++++++++-- tsconfig.json | 5 +- 3 files changed, 123 insertions(+), 23 deletions(-) diff --git a/src/app/features/article-library/components/research-status/research-status.component.html b/src/app/features/article-library/components/research-status/research-status.component.html index a7d41f2f..20dd6e08 100755 --- a/src/app/features/article-library/components/research-status/research-status.component.html +++ b/src/app/features/article-library/components/research-status/research-status.component.html @@ -4,16 +4,18 @@

통일 연구 동향 그래프

-
- - -
+
+ + + + + + + + + + + + diff --git a/src/app/features/article-library/components/research-status/research-status.component.ts b/src/app/features/article-library/components/research-status/research-status.component.ts index f16df18b..2ab134f8 100755 --- a/src/app/features/article-library/components/research-status/research-status.component.ts +++ b/src/app/features/article-library/components/research-status/research-status.component.ts @@ -2,6 +2,7 @@ import { Component, OnInit } from "@angular/core"; import { HttpClient } from "@angular/common/http"; import { ChartConfiguration, ChartOptions, ChartType } from "chart.js"; import { Color, Label, SingleDataSet } from "ng2-charts"; +import * as d3 from 'd3'; import { QueryResponse } from "src/app/core/models/query.response.model"; import { IpService } from "src/app/core/services/ip-service/ip.service"; @@ -28,27 +29,123 @@ export class ResearchStatusComponent implements OnInit { async ngOnInit(): Promise { await this.getTopicCounts(); + this.drawChart(); + } + + drawChart(){ + // set the dimensions and margins of the graph + const width = 450, + height = 450, + margin = 40; + + // The radius of the pieplot is half the width or half the height (smallest one). I subtract a bit of margin. + const radius = Math.min(width, height) / 2 - margin + + // append the svg object to the div called 'my_dataviz' + const svg = d3.select("#pie_chart") + .append("svg") + .attr("width", width) + .attr("height", height) + .append("g") + .attr("transform", `translate(${width/2},${height/2})`); + + // Create dummy data + var value = this.chartData; + var label =this.chartLabels; + + var d = {}; +// const returnedTarget = Object.assign(target, source); + for(var i = 0; i < label.length; i ++){ + d = Object.assign(d, {[label[i]]: value[i]}); + } + const data = d; + + // set the color scale + const color = d3.scaleOrdinal() + .domain(this.chartLabels) + .range(d3.schemeDark2); + + // Compute the position of each group on the pie: + const pie = d3.pie() + .sort(null) // Do not sort group by size + .value(d => d[1]) + const data_ready = pie(Object.entries(data)) + + // The arc generator + const arc = d3.arc() + .innerRadius(radius * 0.5) // This is the size of the donut hole + .outerRadius(radius * 0.8) + + // Another arc that won't be drawn. Just for labels positioning + const outerArc = d3.arc() + .innerRadius(radius * 0.9) + .outerRadius(radius * 0.9) + + // Build the pie chart: Basically, each part of the pie is a path that we build using the arc function. + svg + .selectAll('allSlices') + .data(data_ready) + .join('path') + .attr('d', arc) + .attr('fill', d => color(d.data[1])) + .attr("stroke", "white") + .style("stroke-width", "2px") + .style("opacity", 0.7) + + // Add the polylines between chart and labels: + svg + .selectAll('allPolylines') + .data(data_ready) + .join('polyline') + .attr("stroke", "black") + .style("fill", "none") + .attr("stroke-width", 1) + .attr('points', function(d) { + const posA = arc.centroid(d) // line insertion in the slice + const posB = outerArc.centroid(d) // line break: we use the other arc generator that has been built only for that + const posC = outerArc.centroid(d); // Label position = almost the same as posB + const midangle = d.startAngle + (d.endAngle - d.startAngle) / 2 // we need the angle to see if the X position will be at the extreme right or extreme left + posC[0] = radius * 0.95 * (midangle < Math.PI ? 1 : -1); // multiply by 1 or -1 to put it on the right or on the left + return [posA, posB, posC] + }) + + // Add the polylines between chart and labels: + svg + .selectAll('allLabels') + .data(data_ready) + .join('text') + .text(d => d.data[0]) + .attr('transform', function(d) { + const pos = outerArc.centroid(d); + const midangle = d.startAngle + (d.endAngle - d.startAngle) / 2 + pos[0] = radius * 0.99 * (midangle < Math.PI ? 1 : -1); + return `translate(${pos})`; + }) + .style('text-anchor', function(d) { + const midangle = d.startAngle + (d.endAngle - d.startAngle) / 2 + return (midangle < Math.PI ? 'start' : 'end') + }) } /** - * @description Get topics to show with chart + * @description Get topics to show with chart */ async getTopicCounts() { await this.httpClient .post(this.GET_TOPIC_URL, {}) .toPromise() .then((result: QueryResponse) => { - let items: Array<{ - _id: string; - count: number; - }> = result.payload as Array; + let items: Array<{ _id: string; + count: number;}> = result.payload as Array; + this.chartLabels = items.map((entry) => { - if (entry._id === "") entry._id = "기타"; - return entry._id; - }); + if (entry._id === "") entry._id = "기타"; + return entry._id; + }); + let data = items.map((entry) => { - return entry.count; - }); + return entry.count; + }); this.chartData = data; }); diff --git a/tsconfig.json b/tsconfig.json index 20a49359..4d9b0462 100755 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,7 +20,8 @@ "es2018", "dom" ], - "esModuleInterop": true + "esModuleInterop": true, + "allowJs": true }, - + } From 8b6de99b6e9cd27f6eb2c0f11de9c626784ce64e Mon Sep 17 00:00:00 2001 From: testation21 Date: Thu, 24 Feb 2022 02:27:51 +0900 Subject: [PATCH 2/4] [FIX] ignore unnecessary type matching in typescript --- .../research-status/research-status.component.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/app/features/article-library/components/research-status/research-status.component.ts b/src/app/features/article-library/components/research-status/research-status.component.ts index 2ab134f8..a300c74f 100755 --- a/src/app/features/article-library/components/research-status/research-status.component.ts +++ b/src/app/features/article-library/components/research-status/research-status.component.ts @@ -1,3 +1,5 @@ +// @ts-nocheck + import { Component, OnInit } from "@angular/core"; import { HttpClient } from "@angular/common/http"; import { ChartConfiguration, ChartOptions, ChartType } from "chart.js"; @@ -49,20 +51,20 @@ export class ResearchStatusComponent implements OnInit { .append("g") .attr("transform", `translate(${width/2},${height/2})`); - // Create dummy data + var label = this.chartLabels; var value = this.chartData; - var label =this.chartLabels; var d = {}; // const returnedTarget = Object.assign(target, source); for(var i = 0; i < label.length; i ++){ - d = Object.assign(d, {[label[i]]: value[i]}); + var l:string = "" + label[i]; + d = Object.assign(d, {[l]: value[i]}); } const data = d; // set the color scale const color = d3.scaleOrdinal() - .domain(this.chartLabels) + .domain(label) .range(d3.schemeDark2); // Compute the position of each group on the pie: From eaede65c6377484bf6c32a8bfc696cd950bcce35 Mon Sep 17 00:00:00 2001 From: testation21 Date: Thu, 24 Feb 2022 02:30:01 +0900 Subject: [PATCH 3/4] [REFACTORING] remove unnecessary type casting for avoiding type error in type script --- .../components/research-status/research-status.component.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/app/features/article-library/components/research-status/research-status.component.ts b/src/app/features/article-library/components/research-status/research-status.component.ts index a300c74f..9b07d506 100755 --- a/src/app/features/article-library/components/research-status/research-status.component.ts +++ b/src/app/features/article-library/components/research-status/research-status.component.ts @@ -57,11 +57,9 @@ export class ResearchStatusComponent implements OnInit { var d = {}; // const returnedTarget = Object.assign(target, source); for(var i = 0; i < label.length; i ++){ - var l:string = "" + label[i]; - d = Object.assign(d, {[l]: value[i]}); + d = Object.assign(d, {[label[i]]: value[i]}); } const data = d; - // set the color scale const color = d3.scaleOrdinal() .domain(label) From 0e72b125e6dfa64a31f641117987f5d2a59b6203 Mon Sep 17 00:00:00 2001 From: testation21 Date: Thu, 24 Feb 2022 03:06:10 +0900 Subject: [PATCH 4/4] [FEAT] add keyword analysis per-year functionality --- .../keyword-analysis.component.ts | 44 +++++++++++++++---- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/src/app/features/search-result/components/keyword-analysis/keyword-analysis.component.ts b/src/app/features/search-result/components/keyword-analysis/keyword-analysis.component.ts index 0125182a..8ba20dc1 100644 --- a/src/app/features/search-result/components/keyword-analysis/keyword-analysis.component.ts +++ b/src/app/features/search-result/components/keyword-analysis/keyword-analysis.component.ts @@ -89,6 +89,29 @@ export class KeywordAnalysisComponent implements OnInit, OnDestroy { this.per = "month"; } + getYearData(data){ + var yearData = []; + var tmpYear = "-1"; + var tmpIdx = 0; + for(var i = 0; i < data.length; i ++){ + var year = data[i]["date"].split("\.")[0]; + if(tmpYear == "-1"){ + yearData.push({date: year, freq: data[i]["freq"]}); + tmpYear = year; + } + else if(tmpYear == year){ + yearData[tmpIdx]["freq"] = yearData[tmpIdx]["freq"] + data[i]["freq"]; + } + else { + yearData.push({date: year, freq: data[i]["freq"]}); + tmpIdx++; + tmpYear = year; + } + } + console.log(yearData) + return yearData; + } + async updateChart(){ this.startYearMonth = (document.getElementById("start_month")).value; this.endYearMonth = (document.getElementById("end_month")).value; @@ -133,14 +156,19 @@ export class KeywordAnalysisComponent implements OnInit, OnDestroy { .attr("fill", "#69b3a2"); } //For test - if(this.startYearMonth == "2022-01" && this.endYearMonth == "2022-01"){ - update(dataPerMonth); - } - else if(this.startYearMonth == "2022-01" && this.endYearMonth == "2022-02"){ - update(data1); - } - else if(this.startYearMonth == "2022-02" && this.endYearMonth == "2022-02"){ - update(data2); + if(this.per == "month"){ + if(this.startYearMonth == "2022-01" && this.endYearMonth == "2022-01"){ + update(dataPerMonth); + } + else if(this.startYearMonth == "2022-01" && this.endYearMonth == "2022-02"){ + update(data1); + } + else if(this.startYearMonth == "2022-02" && this.endYearMonth == "2022-02"){ + update(data2); + } + } else{ + var year_data = await this.getYearData(data1); + update(year_data) } }