diff --git a/project/src/app/components/dashboard/show-data/show-data.component.html b/project/src/app/components/dashboard/show-data/show-data.component.html
index f787d4a..e7a003d 100644
--- a/project/src/app/components/dashboard/show-data/show-data.component.html
+++ b/project/src/app/components/dashboard/show-data/show-data.component.html
@@ -15,7 +15,8 @@
-
+
+
diff --git a/project/src/app/components/dashboard/show-data/show-data.component.scss b/project/src/app/components/dashboard/show-data/show-data.component.scss
index 7559198..2b0fc75 100644
--- a/project/src/app/components/dashboard/show-data/show-data.component.scss
+++ b/project/src/app/components/dashboard/show-data/show-data.component.scss
@@ -97,7 +97,7 @@
}
}
#graph-container {
- @apply self-stretch flex-grow m-8 rounded-xl;
+ @apply self-stretch flex-grow m-8 rounded-xl flex flex-row justify-center items-center;
background-color: $secondary-color;
}
}
diff --git a/project/src/app/components/dashboard/show-data/show-data.component.ts b/project/src/app/components/dashboard/show-data/show-data.component.ts
index 7e8f600..0c60892 100644
--- a/project/src/app/components/dashboard/show-data/show-data.component.ts
+++ b/project/src/app/components/dashboard/show-data/show-data.component.ts
@@ -59,6 +59,17 @@ export class ShowDataComponent {
nodes: Node[] = [];
links: Link[] = [];
+ element!: d3.Selection
;
+ svgGroup!: d3.Selection;
+ simulation!: d3.Simulation;
+ link!: d3.Selection;
+ linkLabelsAmount!: d3.Selection;
+ linkLabelsDate!: d3.Selection;
+ linkLabelsType!: d3.Selection;
+ node!: d3.Selection;
+ nodeLabels!: d3.Selection;
+
+
@Output() dataGotEvent = new EventEmitter();
@ViewChild('labelElement') labelElement!: ElementRef;
@@ -67,6 +78,7 @@ export class ShowDataComponent {
@ViewChild('dataElement') dataElement!: ElementRef;
@ViewChild('graphElement') graphElement!: ElementRef;
@ViewChild('contextElement') contextElement!: ElementRef;
+ @ViewChild('searchIdElement') searchIdElement!: ElementRef;
constructor(private userService: UserService, private http: HttpClient, private fetchDataService: FetchDataService) {
this.user = this.userService.getUser();
@@ -82,6 +94,51 @@ export class ShowDataComponent {
return !(this?.inputElement?.nativeElement?.files && this?.inputElement?.nativeElement?.files?.length > 0);
}
+ clearGraphTable(): void {
+ d3.select(this.graphElement.nativeElement).selectAll('*').remove();
+ }
+
+ async handleGetUser() {
+ const response = await this.fetchDataService.fetchDataById(this.searchIdElement.nativeElement.value);
+ if (response.length === 0) {
+ this.graphElement.nativeElement.textContent = "داده ای یافت نشد!";
+ return;
+ }
+ this.nodes = [];
+ this.links = [];
+ this.clearGraphTable();
+ this.nodes.push({
+ x: 1,
+ y: 1,
+ vx: 1,
+ vy: 1,
+ label: Number(this.searchIdElement.nativeElement.value)
+ });
+ for (const item of response) {
+ if (!this.nodes.find(node => node.label === item.accountId)) {
+ this.nodes.push({
+ x: this.nodes[this.nodes.length - 1] ? this.nodes[this.nodes.length - 1].x + 1 : 1,
+ y: this.nodes[this.nodes.length - 1] ? this.nodes[this.nodes.length - 1].y + 1 : 1,
+ vx: 1,
+ vy: 1,
+ label: item.accountId,
+ });
+ }
+ this.links.push({
+ source: item.transactionWithSources[0].sourceAcount === item.accountId ?
+ this.nodes.find(node => node.label === item.accountId)!
+ : this.nodes.find(node => node.label === Number(this.searchIdElement.nativeElement.value))!,
+ target: item.transactionWithSources[0].sourceAcount === item.accountId ?
+ this.nodes.find(node => node.label === Number(this.searchIdElement.nativeElement.value))!
+ : this.nodes.find(node => node.label === item.accountId)!,
+ type: item.transactionWithSources[0].type,
+ amount: (new RialPipePipe()).transform(item.transactionWithSources[0].amount),
+ date: (new PersianDatePipe()).transform(item.transactionWithSources[0].date),
+ })
+ }
+ this.dataGotEvent.emit();
+ }
+
onSubmit(): void {
const formData: FormData = new FormData();
const token: string | null = this.getToken();
@@ -152,36 +209,51 @@ export class ShowDataComponent {
@HostListener('dataGotEvent')
handleGraph(): void {
- const element = d3.select(this.graphElement.nativeElement)
+ this.element = d3.select(this.graphElement.nativeElement)
.append('svg')
.attr('width', this.graphElement.nativeElement.clientWidth)
.attr('height', this.graphElement.nativeElement.clientHeight);
- const svgGroup = element.append('g');
+ this.svgGroup = this.element.append('g');
const zoom = d3.zoom()
.scaleExtent([0.5, 4])
.on('zoom', (event) => {
- svgGroup.attr('transform', event.transform);
+ this.svgGroup.attr('transform', event.transform);
});
- element.call(zoom);
+ this.element.call(zoom);
- const simulation = d3.forceSimulation(this.nodes)
+ this.simulation = d3.forceSimulation(this.nodes)
.force("link", d3.forceLink(this.links));
- const link = svgGroup.append('g')
+ this.svgGroup.append('defs').append('marker')
+ .attr('id', 'arrowhead')
+ .attr('viewBox', '-0 -5 10 10')
+ .attr('refX', 13)
+ .attr('refY', 0)
+ .attr('orient', 'auto')
+ .attr('markerWidth', 12)
+ .attr('markerHeight', 12)
+ .attr('xoverflow', 'visible')
+ .append('svg:path')
+ .attr('d', 'M 0,-5 L 10 ,0 L 0,5')
+ .attr('fill', '#FDFDFD')
+ .style('stroke', 'none');
+
+ this.link = this.svgGroup.append('g')
.attr('class', 'links')
.selectAll('line')
.data(this.links)
.enter()
.append('line')
.attr('stroke-width', 2)
- .attr('stroke', '#FDFDFD');
+ .attr('stroke', '#FDFDFD')
+ .attr('marker-end', 'url(#arrowhead)');
- const linkLabelsAmount = svgGroup.append('g')
+ this.linkLabelsAmount = this.svgGroup.append('g')
.attr('class', 'link-labels')
.selectAll('text')
.data(this.links)
@@ -192,7 +264,7 @@ export class ShowDataComponent {
.attr('style', 'user-select: none;font-weight:bold;font-size:1.5rem;')
.text((d: Link) => d.amount ? d.amount : "");
- const linkLabelsType = svgGroup.append('g')
+ this.linkLabelsType = this.svgGroup.append('g')
.attr('class', 'link-labels')
.selectAll('text')
.data(this.links)
@@ -203,7 +275,7 @@ export class ShowDataComponent {
.attr('style', 'user-select: none;font-weight:bold;font-size:1.5rem;')
.text((d: Link) => d.type ? d.type : "");
- const linkLabelsDate = svgGroup.append('g')
+ this.linkLabelsDate = this.svgGroup.append('g')
.attr('class', 'link-labels')
.selectAll('text')
.data(this.links)
@@ -214,7 +286,7 @@ export class ShowDataComponent {
.attr('style', 'user-select: none;font-weight:bold;font-size:1.5rem;')
.text((d: Link) => d.date ? d.date : "");
- const node = svgGroup.append('g')
+ this.node = this.svgGroup.append('g')
.attr('class', 'nodes')
.selectAll('circle')
.data(this.nodes)
@@ -223,12 +295,23 @@ export class ShowDataComponent {
.attr('r', 10)
.attr('fill', '#002B5B')
.call(d3.drag()
- .on('start', dragStarted)
- .on('drag', dragged)
- .on('end', dragEnded)
+ .on('start', (event: d3.D3DragEvent, d: Node) => {
+ if (!event.active) this.simulation.alphaTarget(0.3).restart();
+ d.fx = d.x;
+ d.fy = d.y;
+ })
+ .on('drag', (event: d3.D3DragEvent, d: Node) => {
+ d.fx = event.x;
+ d.fy = event.y;
+ })
+ .on('end', (event: d3.D3DragEvent, d: Node) => {
+ if (!event.active) this.simulation.alphaTarget(0);
+ d.fx = null;
+ d.fy = null;
+ })
);
- node.on('contextmenu', (event: MouseEvent, d: Node) => {
+ this.node.on('contextmenu', (event: MouseEvent, d: Node) => {
event.preventDefault();
this.contextElement.nativeElement.style.display = 'flex';
@@ -236,7 +319,7 @@ export class ShowDataComponent {
this.contextElement.nativeElement.style.left = event.clientX + 'px';
});
- const nodeLabels = svgGroup.append('g')
+ this.nodeLabels = this.svgGroup.append('g')
.attr('class', 'node-labels')
.selectAll('text')
.data(this.nodes)
@@ -248,59 +331,41 @@ export class ShowDataComponent {
.attr('style', 'user-select: none;font-weight:bold;font-size:1.5rem;')
.text((d: Node) => d.label ? d.label : "");
- function ticked() {
- link
+ this.simulation.on('tick', () => {
+ this.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);
- node
+ this.node
.attr('cx', d => d.x)
.attr('cy', d => d.y);
// Update positions of node labels
- nodeLabels
+ this.nodeLabels
.attr('x', d => d.x)
.attr('y', d => d.y);
// Update positions of link labels
- linkLabelsAmount
+ this.linkLabelsAmount
.attr('x', d => ((d.source as Node).x + (d.target as Node).x) / 2)
.attr('y', d => ((d.source as Node).y + (d.target as Node).y) / 2);
- linkLabelsDate
+ this.linkLabelsDate
.attr('x', d => ((d.source as Node).x + (d.target as Node).x) / 2)
.attr('y', d => ((d.source as Node).y + (d.target as Node).y) / 2 + 20);
- linkLabelsType
+ this.linkLabelsType
.attr('x', d => ((d.source as Node).x + (d.target as Node).x) / 2)
.attr('y', d => ((d.source as Node).y + (d.target as Node).y) / 2 + 40);
- }
-
- simulation.on('tick', ticked);
+ });
- simulation
+ this.simulation
.force('link', d3.forceLink(this.links).id((d, i) => i).distance(250))
.force('charge', d3.forceManyBody().strength(-350))
.force('center', d3.forceCenter(this.graphElement.nativeElement.clientWidth / 2, this.graphElement.nativeElement.clientHeight / 2));
- function dragStarted(event: d3.D3DragEvent, d: Node) {
- if (!event.active) simulation.alphaTarget(0.3).restart();
- d.fx = d.x;
- d.fy = d.y;
- }
-
- function dragged(event: d3.D3DragEvent, d: Node) {
- d.fx = event.x;
- d.fy = event.y;
- }
-
- function dragEnded(event: d3.D3DragEvent, d: Node) {
- if (!event.active) simulation.alphaTarget(0);
- d.fx = null;
- d.fy = null;
- }
}
handleCloseContext() {
diff --git a/project/src/app/services/fetchData/fetch-data.service.ts b/project/src/app/services/fetchData/fetch-data.service.ts
index 09b0403..f61315c 100644
--- a/project/src/app/services/fetchData/fetch-data.service.ts
+++ b/project/src/app/services/fetchData/fetch-data.service.ts
@@ -13,6 +13,18 @@ interface Transaction {
type: string
}
+interface AccountTransaction {
+ transactionWithSources: {
+ transactionID: number;
+ sourceAcount: number;
+ destiantionAccount: number;
+ amount: number;
+ date: string;
+ type: string;
+ }[];
+ accountId: number;
+}
+
@Injectable({
providedIn: 'root'
})
@@ -25,6 +37,11 @@ export class FetchDataService {
return firstValueFrom(this.http.get(API_BASE_URL + 'transactions', {headers: {'Authorization': "Bearer " + token}}))
}
+ fetchDataById(accountId: string): Promise {
+ const token = this.getToken();
+ return firstValueFrom(this.http.get(API_BASE_URL + 'transactions/by-account/' + accountId, {headers: {'Authorization': "Bearer " + token}}))
+ }
+
getToken(): string | null {
if(isPlatformBrowser(this.platform)) {
let token = localStorage.getItem("token");