ngx-deebodata 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +2 -1
- package/ng-package.json +7 -0
- package/package.json +16 -33
- package/src/lib/data-table/charts/bar-graph-component/bar-graph-component.css +156 -0
- package/src/lib/data-table/charts/bar-graph-component/bar-graph-component.html +29 -0
- package/src/lib/data-table/charts/bar-graph-component/bar-graph-component.spec.ts +23 -0
- package/src/lib/data-table/charts/bar-graph-component/bar-graph-component.ts +286 -0
- package/src/lib/data-table/charts/charts-and-graphs/charts-and-graphs.css +27 -0
- package/src/lib/data-table/charts/charts-and-graphs/charts-and-graphs.html +53 -0
- package/src/lib/data-table/charts/charts-and-graphs/charts-and-graphs.spec.ts +23 -0
- package/src/lib/data-table/charts/charts-and-graphs/charts-and-graphs.ts +214 -0
- package/src/lib/data-table/charts/column-chart/column-chart.css +19 -0
- package/src/lib/data-table/charts/column-chart/column-chart.html +47 -0
- package/src/lib/data-table/charts/column-chart/column-chart.spec.ts +23 -0
- package/src/lib/data-table/charts/column-chart/column-chart.ts +178 -0
- package/src/lib/data-table/charts/line-graph-component/line-graph-component.css +33 -0
- package/src/lib/data-table/charts/line-graph-component/line-graph-component.html +59 -0
- package/src/lib/data-table/charts/line-graph-component/line-graph-component.spec.ts +23 -0
- package/src/lib/data-table/charts/line-graph-component/line-graph-component.ts +661 -0
- package/src/lib/data-table/charts/num-value-distro-component/num-value-distro-component.css +61 -0
- package/src/lib/data-table/charts/num-value-distro-component/num-value-distro-component.html +27 -0
- package/src/lib/data-table/charts/num-value-distro-component/num-value-distro-component.spec.ts +23 -0
- package/src/lib/data-table/charts/num-value-distro-component/num-value-distro-component.ts +210 -0
- package/src/lib/data-table/charts/pie-graph-component/pie-graph-component.css +0 -0
- package/src/lib/data-table/charts/pie-graph-component/pie-graph-component.html +15 -0
- package/src/lib/data-table/charts/pie-graph-component/pie-graph-component.spec.ts +23 -0
- package/src/lib/data-table/charts/pie-graph-component/pie-graph-component.ts +197 -0
- package/src/lib/data-table/data-table-module/data-cell/data-cell.css +0 -0
- package/src/lib/data-table/data-table-module/data-cell/data-cell.html +6 -0
- package/src/lib/data-table/data-table-module/data-cell/data-cell.spec.ts +23 -0
- package/src/lib/data-table/data-table-module/data-cell/data-cell.ts +271 -0
- package/src/lib/data-table/data-table-module/data-table-header/data-table-header.css +25 -0
- package/src/lib/data-table/data-table-module/data-table-header/data-table-header.html +55 -0
- package/src/lib/data-table/data-table-module/data-table-header/data-table-header.spec.ts +23 -0
- package/src/lib/data-table/data-table-module/data-table-header/data-table-header.ts +261 -0
- package/src/lib/data-table/data-table-module/data-table-paginator/data-table-paginator.css +69 -0
- package/src/lib/data-table/data-table-module/data-table-paginator/data-table-paginator.html +24 -0
- package/src/lib/data-table/data-table-module/data-table-paginator/data-table-paginator.spec.ts +23 -0
- package/src/lib/data-table/data-table-module/data-table-paginator/data-table-paginator.ts +125 -0
- package/src/lib/data-table/data-table-module/export-component/export-component.css +91 -0
- package/src/lib/data-table/data-table-module/export-component/export-component.html +16 -0
- package/src/lib/data-table/data-table-module/export-component/export-component.spec.ts +23 -0
- package/src/lib/data-table/data-table-module/export-component/export-component.ts +206 -0
- package/src/lib/data-table/data-table-module/ngx-deebodata/ngx-deebodata.css +91 -0
- package/src/lib/data-table/data-table-module/ngx-deebodata/ngx-deebodata.html +193 -0
- package/src/lib/data-table/data-table-module/ngx-deebodata/ngx-deebodata.spec.ts +23 -0
- package/src/lib/data-table/data-table-module/ngx-deebodata/ngx-deebodata.ts +2226 -0
- package/src/lib/data-table/data-table-module/row-group-menu/row-group-menu.css +14 -0
- package/src/lib/data-table/data-table-module/row-group-menu/row-group-menu.html +27 -0
- package/src/lib/data-table/data-table-module/row-group-menu/row-group-menu.spec.ts +23 -0
- package/src/lib/data-table/data-table-module/row-group-menu/row-group-menu.ts +58 -0
- package/src/lib/data-table/data-table-module/row-group-panel/row-group-panel.css +23 -0
- package/src/lib/data-table/data-table-module/row-group-panel/row-group-panel.html +48 -0
- package/src/lib/data-table/data-table-module/row-group-panel/row-group-panel.spec.ts +23 -0
- package/src/lib/data-table/data-table-module/row-group-panel/row-group-panel.ts +599 -0
- package/src/lib/data-table/data-table-module/worker.worker.ts +44 -0
- package/src/lib/interfaces/cell-edit.ts +5 -0
- package/src/lib/interfaces/column-header.ts +10 -0
- package/src/lib/interfaces/column-styles.ts +14 -0
- package/src/lib/interfaces/column-symbol.ts +4 -0
- package/src/lib/interfaces/data-cell.ts +14 -0
- package/src/lib/interfaces/data-row.ts +11 -0
- package/src/lib/interfaces/row-number.ts +4 -0
- package/src/lib/services/common-service.spec.ts +16 -0
- package/src/lib/services/common-service.ts +336 -0
- package/src/lib/services/data-table-service.spec.ts +16 -0
- package/src/lib/services/data-table-service.ts +597 -0
- package/src/lib/services/table-drag-service.spec.ts +16 -0
- package/src/lib/services/table-drag-service.ts +347 -0
- package/src/lib/styles.css +1065 -0
- package/src/public-api.ts +8 -0
- package/tsconfig.lib.json +17 -0
- package/tsconfig.lib.prod.json +11 -0
- package/tsconfig.spec.json +15 -0
- package/fesm2022/ngx-deebodata.mjs +0 -6863
- package/fesm2022/ngx-deebodata.mjs.map +0 -1
- package/types/ngx-deebodata.d.ts +0 -439
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { Component, ElementRef, EventEmitter, Input, Output, signal, ViewChild } from '@angular/core';
|
|
2
|
+
import { CommonService } from '../../../services/common-service';
|
|
3
|
+
import { DataTableService } from '../../../services/data-table-service';
|
|
4
|
+
import { ColumnChart } from '../column-chart/column-chart';
|
|
5
|
+
import { LineGraphComponent } from '../line-graph-component/line-graph-component';
|
|
6
|
+
import { CommonModule } from '@angular/common';
|
|
7
|
+
|
|
8
|
+
@Component({
|
|
9
|
+
selector: 'app-charts-and-graphs',
|
|
10
|
+
imports: [ CommonModule, ColumnChart, LineGraphComponent, ],
|
|
11
|
+
templateUrl: './charts-and-graphs.html',
|
|
12
|
+
styleUrls: ['./charts-and-graphs.css', '../../../styles.css']
|
|
13
|
+
})
|
|
14
|
+
export class ChartsAndGraphs {
|
|
15
|
+
|
|
16
|
+
chartsReady = signal<boolean>(false);
|
|
17
|
+
rowIsAboveFold = signal<number[]>([0]);
|
|
18
|
+
useData: any[] = []
|
|
19
|
+
iRows: any[][] = []
|
|
20
|
+
dtCols: string[] = [];
|
|
21
|
+
numCols: string[] = [];
|
|
22
|
+
filterInfo: string = ""
|
|
23
|
+
currNumColData: any = {}//[] = []
|
|
24
|
+
currNumColNm: any = {}//string = ""
|
|
25
|
+
okBarGraphs: string[] = []
|
|
26
|
+
okLineGraphs: string[] = []
|
|
27
|
+
hasNoData: string[] = []
|
|
28
|
+
@Input() height: string = "";
|
|
29
|
+
@Input() state: string = "all";
|
|
30
|
+
@Input() chartColumns: string[] = [];
|
|
31
|
+
@Output("close") close: EventEmitter<boolean> = new EventEmitter();
|
|
32
|
+
@ViewChild("xChartsBtn", { static: true }) xChartsBtn!: ElementRef<HTMLButtonElement>;
|
|
33
|
+
@ViewChild("chartsTitle", { static: true }) chartsTitle!: ElementRef<HTMLHeadElement>;
|
|
34
|
+
@ViewChild("chartContain", { static: true }) chartContain!: ElementRef<HTMLElement>;
|
|
35
|
+
h2Text: string = ""
|
|
36
|
+
|
|
37
|
+
constructor(public common: CommonService,
|
|
38
|
+
public dataTableService: DataTableService,){ }
|
|
39
|
+
|
|
40
|
+
ngOnInit() {
|
|
41
|
+
if(this.state === "selected")
|
|
42
|
+
this.useData = this.dataTableService.mainData.filter( (d, ind) => this.dataTableService.currSelRows.indexOf(ind) > -1 )
|
|
43
|
+
else
|
|
44
|
+
this.useData = this.dataTableService.currFilData//.filter( d => true )
|
|
45
|
+
this.gatherRelationalPairs()
|
|
46
|
+
const dLen = this.useData.length
|
|
47
|
+
let filinfo = this.dataTableService.getAllFilSrtInfo()?.replace(/Filtered By:/g, "filtered by").
|
|
48
|
+
replace(/ \• Sorted By/g, ", Sorted By").replace(/ \• /g, " ")
|
|
49
|
+
this.h2Text = "Data Insights for <b>"+ dLen.toLocaleString(undefined, {maximumFractionDigits: 0}) + "</b>" +
|
|
50
|
+
(this.state !== 'all' ? (' ' + this.common.titleCase(this.state)) : '') +" Rows ";
|
|
51
|
+
if(filinfo){
|
|
52
|
+
this.h2Text = ("Data Insights " + ("for <b>" + (dLen).toLocaleString(undefined, {maximumFractionDigits:0}) +
|
|
53
|
+
"</b> row" + (dLen === 1 ? " " : "s ")) + filinfo?.split(", Sorted By")[0].trim())
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
ngAfterViewInit() {
|
|
58
|
+
setTimeout( () => {
|
|
59
|
+
let r = 0; let c = 0; let u = 0; let o = 0
|
|
60
|
+
let useChartCols: string[] = [];
|
|
61
|
+
const usableCharts = this.chartColumns.filter( c => !this.hasNoData.includes(c))
|
|
62
|
+
const uclen = usableCharts.length
|
|
63
|
+
const nlen = this.numCols.length
|
|
64
|
+
for(u; u < uclen; u++){
|
|
65
|
+
const ucol = usableCharts[u]
|
|
66
|
+
useChartCols.push(ucol)
|
|
67
|
+
if(this.okBarGraphs.indexOf(ucol) > -1){
|
|
68
|
+
for(o; o < nlen; o++)
|
|
69
|
+
useChartCols.push(ucol + this.dataTableService.bgSep + this.numCols[o])
|
|
70
|
+
o = 0;
|
|
71
|
+
}
|
|
72
|
+
if(this.okLineGraphs.indexOf(ucol) > -1){
|
|
73
|
+
o = 0; let d = 0;
|
|
74
|
+
const dtlen = this.dtCols.length
|
|
75
|
+
for(o; o < nlen; o++){
|
|
76
|
+
for(d; d < dtlen; d++)
|
|
77
|
+
useChartCols.push(ucol + this.dataTableService.bgSep + this.numCols[o] + this.dataTableService.bgSep + this.dtCols[d])
|
|
78
|
+
d = 0;
|
|
79
|
+
}
|
|
80
|
+
o = 0;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const clen = useChartCols.length
|
|
84
|
+
const cWid = this.chartContain.nativeElement.getBoundingClientRect().width
|
|
85
|
+
const insightColsPerRow = cWid < 640 ? 1 : (cWid < 960 ? 2 : 3);
|
|
86
|
+
const rlen = Math.ceil(clen/insightColsPerRow)
|
|
87
|
+
for(r; r < rlen; r++){
|
|
88
|
+
let i = 0
|
|
89
|
+
this.iRows[r] = []
|
|
90
|
+
for(i; i < insightColsPerRow; i++){
|
|
91
|
+
const col = useChartCols[c]
|
|
92
|
+
if(col){
|
|
93
|
+
let title = this.common.titleCase(col)
|
|
94
|
+
let mappedData: any[] = []
|
|
95
|
+
const reg = new RegExp(this.dataTableService.bgSep, "g")
|
|
96
|
+
if(reg.test(col)){
|
|
97
|
+
const bgCols = col.split(this.dataTableService.bgSep)
|
|
98
|
+
const strCol = bgCols[0]
|
|
99
|
+
const numCol = bgCols[1]
|
|
100
|
+
let dtCol
|
|
101
|
+
if(bgCols.length === 3)//line graph
|
|
102
|
+
dtCol = bgCols[2]
|
|
103
|
+
if(!dtCol){//bar graph
|
|
104
|
+
const valsLen = this.dataTableService.dataFilSrtTracker[strCol]?.selDDVals?.filter( (g: any) => g.value !== "(Select All)").length || 0;
|
|
105
|
+
const doAvg = this.useData.length <= valsLen ? "" : "Avg ";
|
|
106
|
+
title = doAvg + this.common.titleCase(numCol) + " by " + this.common.titleCase(strCol);
|
|
107
|
+
mappedData = this.useData.filter( d => d[numCol] === 0 || !!d[numCol]).map( d => ({strColumn: (d[strCol] || "N/A"), numColumn: d[numCol]}))
|
|
108
|
+
} else {//line graph
|
|
109
|
+
title = "Avg " + this.common.titleCase(numCol) + " by " + this.common.titleCase(dtCol);
|
|
110
|
+
mappedData = this.useData.filter( d => d[numCol] === 0 || !!d[numCol]).map( d => ({strColumn: (d[strCol] || "N/A"), numColumn: d[numCol], dtColumn: d[dtCol]}))
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
if(this.dtCols.indexOf(col) > -1){
|
|
114
|
+
const n0 = this.numCols[0]
|
|
115
|
+
title = "Avg " + this.common.titleCase(n0) + " by " + this.common.titleCase(col);
|
|
116
|
+
mappedData = this.useData.filter( d => d[n0] === 0 || !!d[n0]).map( d => ({numColumn: d[n0], dtColumn: d[col]}))
|
|
117
|
+
} else {
|
|
118
|
+
mappedData = this.useData.map( d => d[col] )
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
this.iRows[r].push({ column: col, data: mappedData, title: title })
|
|
122
|
+
c += 1
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
setTimeout( () => { this.chartsReady.set(true) })
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
handleChartScroll(event: any) {
|
|
131
|
+
let abv: number[] = []
|
|
132
|
+
const xBtn= this.xChartsBtn.nativeElement
|
|
133
|
+
const top = event.target.scrollTop
|
|
134
|
+
const rows = document.getElementsByClassName("insight-field-row")
|
|
135
|
+
const rlen = rows.length
|
|
136
|
+
if(xBtn)
|
|
137
|
+
xBtn.style.top = (7 + top) + "px"
|
|
138
|
+
for(var i = (rlen-1); i >= 0; i--){
|
|
139
|
+
const row: HTMLElement = <HTMLElement>rows[i]
|
|
140
|
+
if(this.dataTableService.elIsAboveFold(row, this.dataTableService.tblBot) && !abv.includes(i) && !this.rowIsAboveFold().includes(i))
|
|
141
|
+
abv.push(i)
|
|
142
|
+
}
|
|
143
|
+
this.rowIsAboveFold.set([...this.rowIsAboveFold(), ...abv])
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
gatherRelationalPairs() {//dot plots and bar graphs
|
|
149
|
+
let i = 0;
|
|
150
|
+
const len = this.chartColumns.length
|
|
151
|
+
for(i; i < len; i++){
|
|
152
|
+
const col = this.chartColumns[i]
|
|
153
|
+
const colData = this.useData.map( (d) => d[col] )
|
|
154
|
+
if(colData.every( (d: any) => !d )){
|
|
155
|
+
this.hasNoData.push(col)
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
const yearCol = /(year|yr|fy)/g.test(col?.toLocaleLowerCase())
|
|
159
|
+
const allNumData = colData.every( (d: any) => !d || (d && typeof d === "number") )
|
|
160
|
+
let bitData;
|
|
161
|
+
if(allNumData)
|
|
162
|
+
bitData = colData.every( (d: any) => !d || d === 1 || d === 0 )
|
|
163
|
+
if(allNumData && !yearCol && !bitData){//num data
|
|
164
|
+
this.numCols.push(col);
|
|
165
|
+
} else {
|
|
166
|
+
const dSet =new Set(colData)
|
|
167
|
+
let arrFrmSet: any = []
|
|
168
|
+
dSet.forEach( (v) => arrFrmSet.push(v) )
|
|
169
|
+
const dateData = (colData.every( (d: any) => !d || this.common.isADateObject(d)) && !colData.every( (d: any) => !d ))
|
|
170
|
+
if(dateData || yearCol)
|
|
171
|
+
this.dtCols.push(col)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if(this.numCols.length){
|
|
176
|
+
for(const prop in this.dataTableService.dataFilSrtTracker){
|
|
177
|
+
const trkr = this.dataTableService.dataFilSrtTracker[prop]
|
|
178
|
+
if(trkr && trkr.selDDVals){
|
|
179
|
+
this.okBarGraphs.push(prop)
|
|
180
|
+
if(this.dtCols.length)
|
|
181
|
+
this.okLineGraphs.push(prop)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const dtlen = this.dtCols.length
|
|
187
|
+
if(dtlen && this.numCols.length && typeof document.querySelectorAll === "function"){
|
|
188
|
+
let c = 0
|
|
189
|
+
for(c; c < dtlen; c++){
|
|
190
|
+
const dcol = this.dtCols[c]
|
|
191
|
+
this.currNumColNm[dcol] = this.numCols[0]
|
|
192
|
+
this.currNumColData[dcol] = [1]//this.useData.filter( d => !!d).map( (d) => d[this.numCols[0]] )* this was for dot plot, but now we use line graph
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
setLineGraphNumCol(rowI: number, dtCol: string, col: string) {
|
|
198
|
+
this.currNumColData[dtCol] = []
|
|
199
|
+
setTimeout( () => {
|
|
200
|
+
this.currNumColNm[dtCol] = col
|
|
201
|
+
const irow = this.iRows[rowI].find( i => i.column === dtCol)
|
|
202
|
+
if(irow){
|
|
203
|
+
this.iRows[rowI].find( i => i.column === dtCol).data = this.useData.filter( d => d[col] === 0 || !!d[col]).map( d => ({numColumn: d[col], dtColumn: d[dtCol]}))
|
|
204
|
+
this.iRows[rowI].find( i => i.column === dtCol).title = "Avg " + this.common.titleCase(col) + " by " + this.common.titleCase(dtCol);
|
|
205
|
+
}
|
|
206
|
+
this.currNumColData[dtCol] = [1]//this.useData.filter( d => !!d).map( (d) => d[col] )* this was for dot plot, but now we use line graph
|
|
207
|
+
})
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
closeCharts() {
|
|
211
|
+
this.close.emit(false)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
div{
|
|
2
|
+
box-sizing: border-box;
|
|
3
|
+
padding:7px 7px 0 2px
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.half-wid{
|
|
7
|
+
padding: 0 !important;
|
|
8
|
+
margin: 11px 0 7px 0;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.half-wid span{
|
|
12
|
+
vertical-align: middle;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.vdh-loading{
|
|
16
|
+
text-align: center;
|
|
17
|
+
text-decoration: underline;
|
|
18
|
+
padding: 33px 0px 17px 0px;
|
|
19
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
@if(isNumData){
|
|
2
|
+
<div class="half-wid" [id]="'sum' + common.elifyCol(column)">
|
|
3
|
+
<span class="v-mid">Total: </span><span class="lg-text v-mid{{symCls}}" [attr.data-symbol]="symAttr">{{sum}}</span>
|
|
4
|
+
</div><div class="half-wid" [id]="'avg' + common.elifyCol(column)">
|
|
5
|
+
<span class="v-mid">Avg: </span><span class="lg-text v-mid{{symCls}}" [attr.data-symbol]="symAttr">{{avg}}</span>
|
|
6
|
+
</div><div class="half-wid" [id]="'min' + common.elifyCol(column)">
|
|
7
|
+
<span class="v-mid">Min: </span><span class="lg-text v-mid{{symCls}}" [attr.data-symbol]="symAttr">{{min}}</span>
|
|
8
|
+
</div><div class="half-wid" [id]="'max' + common.elifyCol(column)">
|
|
9
|
+
<span class="v-mid">Max: </span><span class="lg-text v-mid{{symCls}}" [attr.data-symbol]="symAttr">{{max}}</span>
|
|
10
|
+
</div>@if(modeHtml){ <div [innerHTML]="modeHtml"></div>}<div>{{tdivHtml}}</div>
|
|
11
|
+
@if(numValDistMsg){ <div class="vdh-loading">{{numValDistMsg}}</div> }
|
|
12
|
+
@if(numLen < numArrMax){
|
|
13
|
+
<app-num-value-distro-component
|
|
14
|
+
[column]="column"
|
|
15
|
+
[average]="numAvg"
|
|
16
|
+
[originalArr]="colData"
|
|
17
|
+
(done)="numValDistMsg = $event"
|
|
18
|
+
[title]="'Building ' + common.sanitizeUi(common.titleCase(column)) + ' value distribution...'"
|
|
19
|
+
></app-num-value-distro-component>
|
|
20
|
+
}
|
|
21
|
+
} @else {
|
|
22
|
+
@if(nonNumUVal){ <div>{{nonNumUVal}}</div> }
|
|
23
|
+
@if(nonNumEVal){ <div>{{nonNumEVal}}</div> }
|
|
24
|
+
@if(modeHtml){ <div [innerHTML]="modeHtml"></div> }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@if(pieData){
|
|
28
|
+
<app-pie-graph-component
|
|
29
|
+
[uncoverPie]="dataTableService.removePieCovers"
|
|
30
|
+
[data]="pieData"
|
|
31
|
+
[column]="column"
|
|
32
|
+
></app-pie-graph-component>
|
|
33
|
+
}
|
|
34
|
+
@if(isBarGraph){
|
|
35
|
+
<app-bar-graph-component
|
|
36
|
+
[column]="column"
|
|
37
|
+
[data]="colData"
|
|
38
|
+
(title)="handleBgTitle($event)"
|
|
39
|
+
></app-bar-graph-component>
|
|
40
|
+
}
|
|
41
|
+
@if(isLineGraph){
|
|
42
|
+
<app-line-graph-component
|
|
43
|
+
[column]="column"
|
|
44
|
+
[data]="colData"
|
|
45
|
+
(title)="handleBgTitle($event)"
|
|
46
|
+
></app-line-graph-component>
|
|
47
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
2
|
+
|
|
3
|
+
import { ColumnChart } from './column-chart';
|
|
4
|
+
|
|
5
|
+
describe('ColumnChart', () => {
|
|
6
|
+
let component: ColumnChart;
|
|
7
|
+
let fixture: ComponentFixture<ColumnChart>;
|
|
8
|
+
|
|
9
|
+
beforeEach(async () => {
|
|
10
|
+
await TestBed.configureTestingModule({
|
|
11
|
+
imports: [ColumnChart]
|
|
12
|
+
})
|
|
13
|
+
.compileComponents();
|
|
14
|
+
|
|
15
|
+
fixture = TestBed.createComponent(ColumnChart);
|
|
16
|
+
component = fixture.componentInstance;
|
|
17
|
+
fixture.detectChanges();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should create', () => {
|
|
21
|
+
expect(component).toBeTruthy();
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
|
2
|
+
import { CommonService } from '../../../services/common-service';
|
|
3
|
+
import { DataTableService } from '../../../services/data-table-service';
|
|
4
|
+
import { CommonModule } from '@angular/common';
|
|
5
|
+
import { NumValueDistroComponent } from '../num-value-distro-component/num-value-distro-component';
|
|
6
|
+
import { PieGraphComponent } from '../pie-graph-component/pie-graph-component';
|
|
7
|
+
import { BarGraphComponent } from '../bar-graph-component/bar-graph-component';
|
|
8
|
+
import { LineGraphComponent } from '../line-graph-component/line-graph-component';
|
|
9
|
+
|
|
10
|
+
@Component({
|
|
11
|
+
selector: 'app-column-chart',
|
|
12
|
+
imports: [ CommonModule, NumValueDistroComponent, PieGraphComponent, BarGraphComponent, LineGraphComponent, ],
|
|
13
|
+
templateUrl: './column-chart.html',
|
|
14
|
+
styleUrls: ['./column-chart.css', '../../../styles.css']
|
|
15
|
+
})
|
|
16
|
+
export class ColumnChart {
|
|
17
|
+
|
|
18
|
+
isNumData = false
|
|
19
|
+
isBoolData = false
|
|
20
|
+
isBarGraph = false
|
|
21
|
+
isLineGraph = false
|
|
22
|
+
counts: any = null
|
|
23
|
+
sum: string = ""
|
|
24
|
+
avg: string = ""
|
|
25
|
+
min: string = ""
|
|
26
|
+
max: string = ""
|
|
27
|
+
numArrMax: number = 5000
|
|
28
|
+
numAvg: number = 0
|
|
29
|
+
modeHtml: string = ""
|
|
30
|
+
tdivHtml: string = ""
|
|
31
|
+
numValDistMsg = ""
|
|
32
|
+
numLen: number = 0
|
|
33
|
+
symCls: string = ""
|
|
34
|
+
symAttr: string = ""
|
|
35
|
+
pieData: any = null
|
|
36
|
+
nonNumUVal: string = ""
|
|
37
|
+
nonNumEVal: string = ""
|
|
38
|
+
@Input() column: string = ""
|
|
39
|
+
@Input() colData: any[] = []
|
|
40
|
+
@Output("bgTitle") bgTitle: EventEmitter<string> = new EventEmitter();
|
|
41
|
+
|
|
42
|
+
constructor(public common: CommonService,
|
|
43
|
+
public dataTableService: DataTableService,){ }
|
|
44
|
+
|
|
45
|
+
ngOnInit() {
|
|
46
|
+
const reg = new RegExp(this.dataTableService.bgSep, "g")
|
|
47
|
+
if(reg.test(this.column)){
|
|
48
|
+
if(this.column.split(this.dataTableService.bgSep).length > 2){
|
|
49
|
+
this.isLineGraph = true
|
|
50
|
+
} else {
|
|
51
|
+
this.isBarGraph = true
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
this.buildDataInsights()
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
setDistMsg(event: string) {
|
|
59
|
+
this.numValDistMsg = event
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
buildDataInsights() {
|
|
63
|
+
const yearCol = /(year|yr|fy)/g.test(this.column?.toLocaleLowerCase())
|
|
64
|
+
const allNumData = this.colData.every( d => !d || (d && typeof d === "number") )
|
|
65
|
+
const boolData = this.colData.every( d => !d || (d && typeof d === "boolean") )
|
|
66
|
+
this.counts = this.getColumnValueCounts(this.colData)//build pie graphs with this
|
|
67
|
+
let mode = this.getColumnMode(this.counts)
|
|
68
|
+
const disr = (this.counts["n_a"] && this.counts["n_a"] >= mode?.amount) ? " (non-empty value)" : "";
|
|
69
|
+
if(mode && mode.amount != 1){
|
|
70
|
+
try{
|
|
71
|
+
let maxChars = 75
|
|
72
|
+
let pMdTxt = this.common.sanitizeUi(mode.text)
|
|
73
|
+
if(mode.text && typeof mode.text === "string"){
|
|
74
|
+
pMdTxt = mode.text.substring(0, maxChars)
|
|
75
|
+
if(pMdTxt.length === maxChars){
|
|
76
|
+
try{
|
|
77
|
+
pMdTxt = (pMdTxt.trim() + "...")
|
|
78
|
+
if(pMdTxt.startsWith("\""))
|
|
79
|
+
pMdTxt += "\""
|
|
80
|
+
if(pMdTxt.startsWith("'"))
|
|
81
|
+
pMdTxt += "'"
|
|
82
|
+
}catch(e){}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if(pMdTxt && allNumData && !yearCol)
|
|
86
|
+
pMdTxt = parseInt(pMdTxt).toLocaleString(undefined, { maximumFractionDigits: 3 })
|
|
87
|
+
const modeVal = mode.amount.toLocaleString(undefined, { maximumFractionDigits: 0 })
|
|
88
|
+
this.modeHtml = "Most frequent"+disr+": <i>" + pMdTxt + "</i> appears " + modeVal + " time" + (mode.amount === 1 ? "" : "s");
|
|
89
|
+
}catch(e){}
|
|
90
|
+
}
|
|
91
|
+
let bitData;
|
|
92
|
+
if(allNumData)
|
|
93
|
+
bitData = this.colData.every( d => !d || d === 1 || d === 0 )
|
|
94
|
+
if(allNumData && !yearCol && !bitData){//num data
|
|
95
|
+
let isCurr = false
|
|
96
|
+
this.isNumData = true
|
|
97
|
+
const numsOnly = this.colData.filter( d => !!d || (d === 0) )
|
|
98
|
+
this.numLen = numsOnly.length
|
|
99
|
+
let usum = numsOnly.reduce( (acc, curr) => (acc + curr), 0)
|
|
100
|
+
const lastNum = numsOnly[numsOnly.length-1]
|
|
101
|
+
const sym = this.dataTableService.dataFilSrtTracker[this.column]?.colCellSymbol;
|
|
102
|
+
if(sym){
|
|
103
|
+
isCurr = ["$","€","£","¥","₣","₹"].indexOf(sym) > -1
|
|
104
|
+
this.symCls = isCurr ? ' has-symbol-b' : ' has-symbol';
|
|
105
|
+
this.symAttr = sym;
|
|
106
|
+
}
|
|
107
|
+
if(typeof lastNum === "number" && ((usum - lastNum === lastNum) || (Math.abs(usum - lastNum*2) < 0.02))){//check that a total column is already there
|
|
108
|
+
usum -= lastNum
|
|
109
|
+
this.numLen -= 1
|
|
110
|
+
this.tdivHtml = "We omitted the last value from the total because it may be a total row itself.";
|
|
111
|
+
}
|
|
112
|
+
this.numAvg = (usum/this.numLen)
|
|
113
|
+
this.sum = usum.toLocaleString(undefined, { maximumFractionDigits: 2, minimumFractionDigits: (isCurr ? 2 : 0) });
|
|
114
|
+
this.avg = this.numAvg.toLocaleString(undefined, { maximumFractionDigits: 2, minimumFractionDigits: (isCurr ? 2 : 0) });
|
|
115
|
+
this.min = Math.min(...numsOnly).toLocaleString(undefined, { maximumFractionDigits: 2, minimumFractionDigits: (isCurr ? 2 : 0) });
|
|
116
|
+
this.max = Math.max(...(this.tdivHtml ? numsOnly.filter( (n, ind) => ind < (numsOnly.length-1) ) : numsOnly)).
|
|
117
|
+
toLocaleString(undefined, { maximumFractionDigits: 2, minimumFractionDigits: (isCurr ? 2 : 0) });
|
|
118
|
+
if(this.numLen && this.numLen < this.numArrMax)
|
|
119
|
+
this.numValDistMsg = "Building " + this.common.sanitizeUi(this.common.titleCase(this.column)) + " value distribution..."
|
|
120
|
+
} else {
|
|
121
|
+
const dSet =new Set(this.colData)
|
|
122
|
+
let arrFrmSet: any[] = []
|
|
123
|
+
dSet.forEach( (v) => { arrFrmSet.push(v) })
|
|
124
|
+
this.nonNumUVal = arrFrmSet.length.toLocaleString(undefined, { maximumFractionDigits: 0 }) + " unique value" + (dSet.size === 1 ? "" : "s")
|
|
125
|
+
const empVals = this.colData.filter( (d) => !d).length
|
|
126
|
+
this.nonNumEVal = empVals.toLocaleString(undefined, { maximumFractionDigits: 0 }) + " empty value" + (empVals === 1 ? "" : "s");
|
|
127
|
+
const cantDoDD = arrFrmSet.some( a => a && a.length > 40 )
|
|
128
|
+
if(!cantDoDD && ((boolData || bitData) || (arrFrmSet.filter( (a) => !!a ).length > 0 && this.colData.every( (d) => !d || typeof d === "string")))){
|
|
129
|
+
if(arrFrmSet.length <= 18)
|
|
130
|
+
this.pieData = { counts: this.counts, type: ((boolData || bitData) ? "boolean" : "string") }
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
getColumnValueCounts(data: any[]) {
|
|
136
|
+
let i = 0
|
|
137
|
+
let count: any = {}
|
|
138
|
+
const na = "n_a"
|
|
139
|
+
const len = data.length
|
|
140
|
+
for(i; i < len; i++){
|
|
141
|
+
const d = data[i]
|
|
142
|
+
const val = (d && typeof d) === "string" ? d :
|
|
143
|
+
(this.common.isADateObject(d) ? d.toLocaleDateString() : d)
|
|
144
|
+
if(!val || typeof val === "undefined" || this.dataTableService.badStrings.indexOf(val) > -1){
|
|
145
|
+
if(!count[na] || typeof count[na] === "undefined")
|
|
146
|
+
count[na] = 1
|
|
147
|
+
else
|
|
148
|
+
count[na] += 1
|
|
149
|
+
continue
|
|
150
|
+
}
|
|
151
|
+
if(!count[val] || typeof count[val] === "undefined")
|
|
152
|
+
count[val] = 1
|
|
153
|
+
else
|
|
154
|
+
count[val] += 1
|
|
155
|
+
}
|
|
156
|
+
count[this.dataTableService.deboTotal] = len
|
|
157
|
+
return count;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
getColumnMode(count: any) {
|
|
161
|
+
let most = null
|
|
162
|
+
const na = "n_a"
|
|
163
|
+
for(const prop in count){
|
|
164
|
+
if(prop === this.dataTableService.deboTotal || prop === na)
|
|
165
|
+
continue
|
|
166
|
+
if(!most)
|
|
167
|
+
most = {text: prop, amount: count[prop]}
|
|
168
|
+
if(count[prop] > most.amount)
|
|
169
|
+
most = {text: prop, amount: count[prop]}
|
|
170
|
+
}
|
|
171
|
+
return most
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
handleBgTitle(event: string) {
|
|
175
|
+
this.bgTitle.emit(event)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
:host{
|
|
2
|
+
--accent-color:rgb(50, 50, 50);
|
|
3
|
+
--grid-color:rgb(199, 199, 199);
|
|
4
|
+
--pad-left-base: 33px;
|
|
5
|
+
--row-num-width: 75px;
|
|
6
|
+
--reg-font-size: 17px
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.lg-plot-opq{
|
|
10
|
+
opacity: 0;
|
|
11
|
+
background: white;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.lg-plot-opq:hover{
|
|
15
|
+
opacity: 1 !important;
|
|
16
|
+
background: var(--grid-color);
|
|
17
|
+
transition: opacity 0.2s ease;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.lg-canvas{
|
|
21
|
+
top: 0;
|
|
22
|
+
left: 0;
|
|
23
|
+
bottom: 0;
|
|
24
|
+
position: absolute;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.lg-msg{
|
|
28
|
+
top: 0;
|
|
29
|
+
left: 0;
|
|
30
|
+
right: 0;
|
|
31
|
+
position: absolute;
|
|
32
|
+
color: var(--accent-color);
|
|
33
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<div #statOptsCont class="center stat-opt-contain">
|
|
2
|
+
@for(stat of statOpts; track stat){
|
|
3
|
+
<div [style.width]="statBtnWid" class="inline-b center">
|
|
4
|
+
<button class="no-btn stat-opt-btn" (click)="handleStatChange(stat)"
|
|
5
|
+
[class.stat-opt-btn-active]="selStat === stat">{{common.titleCase(stat)}}</button>
|
|
6
|
+
</div>
|
|
7
|
+
}
|
|
8
|
+
</div>
|
|
9
|
+
<div #auxOptsCont style="padding-bottom: 17px;">
|
|
10
|
+
<div class="qtr-wid center v-mid">
|
|
11
|
+
@if(valOpts().length){
|
|
12
|
+
<select [name]="'valSel' + common.elifyCol(column)" [id]="'valSel' + common.elifyCol(column)" [(ngModel)]="selDdVal"
|
|
13
|
+
(change)="handleValSelChange($any($event).target.value)" style="max-width: 80%">
|
|
14
|
+
@for(o of valOpts(); track o){
|
|
15
|
+
<option [value]="o" [selected]="o === selDdVal">{{o}}</option>
|
|
16
|
+
}
|
|
17
|
+
</select>
|
|
18
|
+
}
|
|
19
|
+
</div><div class="qtr-wid center v-mid">
|
|
20
|
+
<select [name]="'timeDiff' + common.elifyCol(column)" [id]="'timeDiff' + common.elifyCol(column)"
|
|
21
|
+
(change)="handleTimeDiffChange($any($event).target.value)" style="max-width: 88%" [(ngModel)]="selTDOpt">
|
|
22
|
+
<option value="-1" [selected]="selTDOpt === -1">All time</option>
|
|
23
|
+
@for(t of timeDiffOpts; track t.val){
|
|
24
|
+
<option [value]="t.val" [selected]="t.val === selTDOpt">{{t.text}}</option>
|
|
25
|
+
}
|
|
26
|
+
</select>
|
|
27
|
+
</div><div class="half-wid v-mid center dontwrap {{currentDeltaCls}}" [innerHTML]="currentDelta"></div>
|
|
28
|
+
</div>
|
|
29
|
+
<div #lgContain [id]="'lineGraph' + common.elifyCol(column)" class="inner-bg-contain" style="margin-bottom: 33px;">
|
|
30
|
+
<div #yAxis class="lg-n-marker-cont" [style.height]="totalHgt + 'px'">
|
|
31
|
+
@for(n of naddedtks(); track n.value; let i = $index){
|
|
32
|
+
<div class="bg-dep-var" [class.invisible]="n.value && n.value === maxYVal" [attr.data-number]="n.value"
|
|
33
|
+
[style.padding-bottom]="i < 4 ? (((i*4)+1) + 'px') : '0px'">
|
|
34
|
+
{{(n.value === '0') ? '' : n.value}}
|
|
35
|
+
</div>
|
|
36
|
+
}
|
|
37
|
+
@for(n of nmrks; track n){
|
|
38
|
+
<div class="lg-n-marker-h">{{n.value}}</div>
|
|
39
|
+
}
|
|
40
|
+
</div><div #lineGraph class="line-graph" [style.height]="totalHgt + 'px'">
|
|
41
|
+
@for(n of dtmrks; track n){
|
|
42
|
+
<div class="lg-dt-marker" [class.visible-marker]="n.visible" [style.left]="n.left">{{n.value}}</div>
|
|
43
|
+
}
|
|
44
|
+
@for(n of dtmrksH; track n){
|
|
45
|
+
<div class="lg-dt-marker-h" [class.visible-marker]="n.visible" [style.left]="n.left">{{n.value}}</div>
|
|
46
|
+
}
|
|
47
|
+
@if(lblTxt){
|
|
48
|
+
<div class="lg-lbl-x">{{lblTxt}}</div>
|
|
49
|
+
}
|
|
50
|
+
<canvas #lgCanvas class="lg-canvas" [height]="totalHgt"></canvas>
|
|
51
|
+
@for(p of plots(); track $index){
|
|
52
|
+
<div class="lg-plot lg-plot-opq" [attr.data-number]="p.number" (mouseenter)="showDotPlotInfo(p.date, p.number)"
|
|
53
|
+
(mouseleave)="hideDotPlotInfo()" [ngStyle]="{'bottom': (p.bottom-4) + 'px', 'left': (p.left-4) + 'px' }">{{p.value}}</div>
|
|
54
|
+
}
|
|
55
|
+
@if(!plots().length){
|
|
56
|
+
<div class="flex-center lg-msg" [style.height]="totalHgt + 'px'"><div>{{graphMsg}}</div></div>
|
|
57
|
+
}
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
2
|
+
|
|
3
|
+
import { LineGraphComponent } from './line-graph-component';
|
|
4
|
+
|
|
5
|
+
describe('LineGraphComponent', () => {
|
|
6
|
+
let component: LineGraphComponent;
|
|
7
|
+
let fixture: ComponentFixture<LineGraphComponent>;
|
|
8
|
+
|
|
9
|
+
beforeEach(async () => {
|
|
10
|
+
await TestBed.configureTestingModule({
|
|
11
|
+
imports: [LineGraphComponent]
|
|
12
|
+
})
|
|
13
|
+
.compileComponents();
|
|
14
|
+
|
|
15
|
+
fixture = TestBed.createComponent(LineGraphComponent);
|
|
16
|
+
component = fixture.componentInstance;
|
|
17
|
+
fixture.detectChanges();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should create', () => {
|
|
21
|
+
expect(component).toBeTruthy();
|
|
22
|
+
});
|
|
23
|
+
});
|