diff --git a/src/app/modules/salaries/components/salaries-by-grades-chart/salaries-by-grades-chart.component.html b/src/app/modules/salaries/components/salaries-by-grades-chart/salaries-by-grades-chart.component.html
index 67ec9d5e..fe4794c5 100644
--- a/src/app/modules/salaries/components/salaries-by-grades-chart/salaries-by-grades-chart.component.html
+++ b/src/app/modules/salaries/components/salaries-by-grades-chart/salaries-by-grades-chart.component.html
@@ -1,3 +1,28 @@
-
-
+
+
{{ title }}
+
+
+
+
Кол-во анкет
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/app/modules/salaries/components/salaries-by-grades-chart/salaries-by-grades-chart.component.scss b/src/app/modules/salaries/components/salaries-by-grades-chart/salaries-by-grades-chart.component.scss
index bcf541aa..9f768937 100644
--- a/src/app/modules/salaries/components/salaries-by-grades-chart/salaries-by-grades-chart.component.scss
+++ b/src/app/modules/salaries/components/salaries-by-grades-chart/salaries-by-grades-chart.component.scss
@@ -1,3 +1,14 @@
#canvas {
min-height: 400px;
}
+
+#canvas-container {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ min-height: 400px;
+}
+
+.list-group-item {
+ font-size: smaller;
+}
\ No newline at end of file
diff --git a/src/app/modules/salaries/components/salaries-by-grades-chart/salaries-by-grades-chart.component.ts b/src/app/modules/salaries/components/salaries-by-grades-chart/salaries-by-grades-chart.component.ts
index 33a08198..ca4e025c 100644
--- a/src/app/modules/salaries/components/salaries-by-grades-chart/salaries-by-grades-chart.component.ts
+++ b/src/app/modules/salaries/components/salaries-by-grades-chart/salaries-by-grades-chart.component.ts
@@ -7,6 +7,9 @@ import { SalariesChart } from '../salaries-chart/salaries-chart';
import { Chart, ChartType } from 'chart.js/auto';
import { RandomRgbColor } from './random-rgb-color';
import { UserProfession } from '@models/salaries/user-profession';
+import { SalariesChartJsObject } from './salaries-chart-js-object';
+import { SalariesByMoneyBarChart } from '@services/user-salaries.service';
+import { SalariesPerProfession } from '../salaries-per-profession';
@Component({
selector: 'app-salaries-by-grades-chart',
@@ -16,76 +19,47 @@ import { UserProfession } from '@models/salaries/user-profession';
export class SalariesByGradesChartComponent implements OnInit, OnDestroy {
@Input()
- chart: SalariesChart | null = null;
+ chart: SalariesByMoneyBarChart | null = null;
- chartData: Chart | null = null;
+ @Input()
+ title: string | null = null;
- constructor() {}
+ @Input()
+ salaries: Array
| null = null;
- ngOnInit(): void {
- if (this.chart == null ||
- this.chart.salariesByMoneyBarChart == null) {
- return;
- }
+ chartDataLocal: SalariesChartJsObject | null = null;
- const chartData = this.chart.salariesByMoneyBarChart;
- const randomColor = new RandomRgbColor();
- const datasets = [
- {
- type: 'line' as ChartType,
- label: 'Все',
- data: chartData.items.map(x => x.count),
- borderWidth: 3,
- borderColor: randomColor.toString(1),
- backgroundColor: randomColor.toString(0.5),
- },
- ];
+ readonly canvasId = 'canvas_' + Math.random().toString(36).substring(7);
- chartData.itemsByProfession.forEach((x, i) => {
- const profession = x.profession;
- const color = new RandomRgbColor();
- datasets.push({
- label: UserProfession[profession].toString(),
- data: x.items.map(x => x.count),
- borderWidth: 1,
- borderColor: color.toString(0.6),
- backgroundColor: color.toString(0.3),
- type: 'bar' as ChartType,
- });
- });
+ constructor() {}
- this.chartData = new Chart('canvas', {
- type: 'scatter',
- data: {
- labels: chartData.labels
- .map(x => {
- let num = Number(x);
- if (isNaN(num)) {
- throw Error('Invalid label ' + x);
- }
+ ngOnInit(): void {
+ // ignored
+ }
- num = num / 1000;
- return num + 'k';
- }),
- datasets: datasets,
- },
- options: {
- maintainAspectRatio: false,
- scales: {
- y: {
- beginAtZero: true,
- },
- },
- elements: {
- line: {
- tension: 0.4
- },
- }
- },
- });
+ ngAfterViewInit() {
+ this.initChart();
}
ngOnDestroy(): void {
// ignored
}
+
+ toggleBarDatasetByProfession(profession: UserProfession): void {
+ this.chartDataLocal?.toggleDatasetByProfession(profession);
+ }
+
+ private initChart(): void {
+ if (this.chart == null || this.salaries == null) {
+ return;
+ }
+
+ this.chartDataLocal = new SalariesChartJsObject(this.canvasId, this.chart);
+ this.chartDataLocal.hideBarDatasets();
+
+ var chartEl = document.getElementById(this.canvasId);
+ if (chartEl != null && chartEl.parentElement != null) {
+ chartEl.style.height = chartEl?.parentElement.style.height ?? '100%';
+ }
+ }
}
diff --git a/src/app/modules/salaries/components/salaries-by-grades-chart/salaries-chart-js-object.ts b/src/app/modules/salaries/components/salaries-by-grades-chart/salaries-chart-js-object.ts
new file mode 100644
index 00000000..de698ec3
--- /dev/null
+++ b/src/app/modules/salaries/components/salaries-by-grades-chart/salaries-chart-js-object.ts
@@ -0,0 +1,114 @@
+import { UserProfession } from '@models/salaries/user-profession';
+import { SalariesByMoneyBarChart } from '@services/user-salaries.service';
+import { Chart, ChartType } from 'chart.js/auto';
+import { RandomRgbColor } from './random-rgb-color';
+
+interface ChartDatasetType {
+ profession: UserProfession | null;
+ label: string;
+ data: Array;
+ borderWidth: number;
+ borderColor: string;
+ backgroundColor: string;
+ type: ChartType;
+}
+
+export class SalariesChartJsObject extends Chart {
+
+ private readonly datasets: Array = [];
+
+ constructor(canvasId: string, chartData: SalariesByMoneyBarChart) {
+
+ const ctx = document.getElementById(canvasId) as HTMLCanvasElement;
+ const randomColor = new RandomRgbColor();
+ const datasets: Array = [
+ {
+ profession: null,
+ type: 'line' as ChartType,
+ label: 'Все',
+ data: chartData.items.map(x => x.count),
+ borderWidth: 3,
+ borderColor: randomColor.toString(1),
+ backgroundColor: randomColor.toString(0.5),
+ },
+ ];
+
+ chartData.itemsByProfession.forEach((x, i) => {
+ const color = new RandomRgbColor();
+ datasets.push({
+ profession: x.profession,
+ label: UserProfession[x.profession].toString(),
+ data: x.items.map(x => x.count),
+ borderWidth: 1,
+ borderColor: color.toString(0.6),
+ backgroundColor: color.toString(0.3),
+ type: 'bar' as ChartType,
+ });
+ });
+
+ super(
+ canvasId,
+ {
+ type: 'scatter',
+ data: {
+ labels: chartData.labels
+ .map(x => {
+ let num = Number(x);
+ if (isNaN(num)) {
+ throw Error('Invalid label ' + x);
+ }
+
+ num = num / 1000;
+ return num + 'k';
+ }),
+ datasets: datasets,
+ },
+ options: {
+ responsive: true,
+ maintainAspectRatio: false,
+ scales: {
+ y: {
+ beginAtZero: true,
+ },
+ },
+ elements: {
+ line: {
+ tension: 0.4
+ },
+ },
+ plugins: {
+ legend: {
+ position: 'bottom',
+ title: {
+ position: 'start',
+ },
+ }
+ }
+ },
+ });
+
+ this.datasets = datasets;
+ }
+
+ hideBarDatasets(): void {
+ for (let index = 0; index < this.datasets.length; index++) {
+ const dataset = this.datasets[index];
+ if (dataset.type == 'bar') {
+ this.setDatasetVisibility(index, false);
+ }
+ }
+
+ this.update();
+ }
+
+ toggleDatasetByProfession(profession: UserProfession): void {
+ const index = this.datasets.findIndex(x => x.profession == profession);
+ if (index == -1) {
+ return;
+ }
+
+ const visibility = !this.isDatasetVisible(index);
+ this.setDatasetVisibility(index, visibility);
+ this.update();
+ }
+}
\ No newline at end of file
diff --git a/src/app/modules/salaries/components/salaries-chart/salaries-chart.component.html b/src/app/modules/salaries/components/salaries-chart/salaries-chart.component.html
index 2b11766e..22a9f123 100644
--- a/src/app/modules/salaries/components/salaries-chart/salaries-chart.component.html
+++ b/src/app/modules/salaries/components/salaries-chart/salaries-chart.component.html
@@ -21,87 +21,86 @@
-
+
-
-
-
-
Квалификации:
-
-
+
+
+
Демонстрационные данные:
+
+
+
-
-
-
-
Действия:
-
-
+
+
+
+
-
+
-
-
-
-
Демонстрационные данные:
-
-
-
-
-
-
-
-
-
Рассчитано на основании {{ salariesChart.countOfRecords }} анкет(ы)
-
-
-
-
Данные сгенерированы для демонстрации.
- для получения актуальных данных.
+
+
+
+
+ Рассчитано на основании {{ salariesChart.countOfRecords }} анкет(ы)
+
-
-
+
+
+
+
Данные сгенерированы для демонстрации.
+ для получения актуальных данных.
+
+
+
+
- По всем IT-специалистам
+ Графики
(демонстрационные данные)
-
+
TODO: в скором времени будет добавлена возможность фильтрации данных по специальностям, грейдам и фреймворкам/языкам программирования
-
-
+
+
+
+
+
+
+
+ Идея подсмотрена у сервиса
Habr.Карьера, за что им большая благодарность.
-
-
- Идея подсмотрена у сервиса
Habr.Карьера, за что им большая благодарность.
-
+
diff --git a/src/app/modules/salaries/components/salaries-chart/salaries-chart.component.ts b/src/app/modules/salaries/components/salaries-chart/salaries-chart.component.ts
index b7e85c3e..7f536d56 100644
--- a/src/app/modules/salaries/components/salaries-chart/salaries-chart.component.ts
+++ b/src/app/modules/salaries/components/salaries-chart/salaries-chart.component.ts
@@ -16,6 +16,7 @@ import { StubSalariesChart } from './stub-salaries-chart';
export class SalariesChartComponent implements OnInit, OnDestroy {
salariesChart: SalariesChart | null = null;
+
showDataStub = false;
openAddSalaryModal = false;
isAuthenticated = false;
diff --git a/src/app/modules/salaries/components/salaries-chart/salaries-chart.ts b/src/app/modules/salaries/components/salaries-chart/salaries-chart.ts
index 249096d9..bcc64f0f 100644
--- a/src/app/modules/salaries/components/salaries-chart/salaries-chart.ts
+++ b/src/app/modules/salaries/components/salaries-chart/salaries-chart.ts
@@ -1,5 +1,6 @@
import { formatNumber } from "@angular/common";
-import { SalariesByMoneyBarChart, SalariesByProfession, SalariesChartResponse } from "@services/user-salaries.service";
+import { SalariesByMoneyBarChart, SalariesChartResponse } from "@services/user-salaries.service";
+import { SalariesPerProfession } from "../salaries-per-profession";
export class SalariesChart {
@@ -10,8 +11,14 @@ export class SalariesChart {
readonly medianRemoteSalary: string | null;
readonly countOfRecords: number;
- readonly salariesByProfession: Array
;
+
readonly salariesByMoneyBarChart: SalariesByMoneyBarChart | null;
+ readonly salariesByMoneyBarChartForRemote: SalariesByMoneyBarChart | null;
+
+ readonly salariesPerProfessionForLocal: Array | null;
+ readonly salariesPerProfessionForRemote: Array | null;
+
+ readonly hasRemoteSalaries: boolean;
constructor(readonly data: SalariesChartResponse) {
this.averageSalary = SalariesChart.formatNumber(data.averageSalary) ?? '';
@@ -21,8 +28,15 @@ export class SalariesChart {
this.medianRemoteSalary = SalariesChart.formatNumber(data.medianRemoteSalary)
this.countOfRecords = data.salaries.length;
- this.salariesByProfession = data.salariesByProfession;
+
this.salariesByMoneyBarChart = data.salariesByMoneyBarChart;
+ this.salariesByMoneyBarChartForRemote = data.salariesByMoneyBarChartForRemote;
+
+ const salariesPerProfession = SalariesPerProfession.from(data.salaries);
+
+ this.salariesPerProfessionForLocal = salariesPerProfession.local;
+ this.salariesPerProfessionForRemote = salariesPerProfession.remote;
+ this.hasRemoteSalaries = this.salariesPerProfessionForRemote.length > 0;
}
private static formatNumber(value: number | null): string | null {
diff --git a/src/app/modules/salaries/components/salaries-chart/salary-block-value/salary-block-value.component.html b/src/app/modules/salaries/components/salaries-chart/salary-block-value/salary-block-value.component.html
index 9ada4904..429d17ab 100644
--- a/src/app/modules/salaries/components/salaries-chart/salary-block-value/salary-block-value.component.html
+++ b/src/app/modules/salaries/components/salaries-chart/salary-block-value/salary-block-value.component.html
@@ -1,7 +1,7 @@
{{ title }}
- {{ value }}
+ {{ value }}
тг.
\ No newline at end of file
diff --git a/src/app/modules/salaries/components/salaries-chart/stub-salaries-chart.ts b/src/app/modules/salaries/components/salaries-chart/stub-salaries-chart.ts
index d4b30598..806b3233 100644
--- a/src/app/modules/salaries/components/salaries-chart/stub-salaries-chart.ts
+++ b/src/app/modules/salaries/components/salaries-chart/stub-salaries-chart.ts
@@ -41,7 +41,6 @@ export class StubSalariesChart extends SalariesChart {
medianRemoteSalary: StubSalariesChart.getRandomNumber(1_200, 600) * 1000,
shouldAddOwnSalary: true,
salaries: salaries,
- salariesByProfession: [],
salariesByMoneyBarChart: {
items: StubSalariesChart.salaryLabels.map((x) => {
const value = parseInt(x);
@@ -54,6 +53,7 @@ export class StubSalariesChart extends SalariesChart {
itemsByProfession: [],
labels: StubSalariesChart.salaryLabels,
},
+ salariesByMoneyBarChartForRemote: null,
rangeStart: new Date(),
rangeEnd: new Date(),
});
diff --git a/src/app/modules/salaries/components/salaries-per-profession.ts b/src/app/modules/salaries/components/salaries-per-profession.ts
new file mode 100644
index 00000000..ec7a572f
--- /dev/null
+++ b/src/app/modules/salaries/components/salaries-per-profession.ts
@@ -0,0 +1,53 @@
+import { CompanyType } from "@models/salaries/company-type";
+import { UserSalary } from "@models/salaries/salary.model";
+import { UserProfession } from "@models/salaries/user-profession";
+import { SplittedByWhitespacesString } from "@shared/value-objects/splitted-by-whitespaces-string";
+
+export class SalariesPerProfession {
+
+ readonly professionName: string;
+ constructor(
+ readonly profession: UserProfession,
+ readonly items: Array) {
+ this.professionName = new SplittedByWhitespacesString(UserProfession[profession]).toString();
+ }
+
+ static from(salaries: Array): {
+ local: Array,
+ remote: Array
+ } {
+
+ const localSalaries: Array = [];
+ const remoteSalaries: Array = [];
+
+ for (let index = 0; index < salaries.length; index++) {
+ const salary = salaries[index];
+ if (salary.company == CompanyType.Local) {
+ localSalaries.push(salary);
+ } else {
+ remoteSalaries.push(salary);
+ }
+ }
+
+ var uniqueProfessionsForLocal = [...new Set(localSalaries.map(x => x.profession))];
+ var uniqueProfessionsForRemote = [...new Set(remoteSalaries.map(x => x.profession))];
+
+ console.log(uniqueProfessionsForLocal);
+ console.log(uniqueProfessionsForRemote);
+
+ const local = uniqueProfessionsForLocal.map(x => {
+ const filteredSalaries = localSalaries.filter(salary => salary.profession == x);
+ return new SalariesPerProfession(x, filteredSalaries);
+ });
+
+ const remote = uniqueProfessionsForRemote.map(x => {
+ const filteredSalaries = remoteSalaries.filter(salary => salary.profession == x);
+ return new SalariesPerProfession(x, filteredSalaries);
+ });
+
+ return {
+ local: local.sort((a, b) => a.items.length > b.items.length ? -1 : 1),
+ remote: remote.sort((a, b) => a.items.length > b.items.length ? -1 : 1),
+ };
+ }
+}
diff --git a/src/app/services/user-salaries.service.ts b/src/app/services/user-salaries.service.ts
index 18510be5..8ad95cb6 100644
--- a/src/app/services/user-salaries.service.ts
+++ b/src/app/services/user-salaries.service.ts
@@ -31,13 +31,8 @@ export interface SalariesChartResponse {
averageRemoteSalary: number | null;
medianRemoteSalary: number | null;
- salariesByProfession: SalariesByProfession[];
salariesByMoneyBarChart: SalariesByMoneyBarChart | null;
-}
-
-export interface SalariesByProfession {
- profession: UserProfession;
- salaries: UserSalary[];
+ salariesByMoneyBarChartForRemote: SalariesByMoneyBarChart | null;
}
export interface SalariesByProfessionMoneyBarChartItem {
diff --git a/src/app/shared/value-objects/splitted-by-whitespaces-string.ts b/src/app/shared/value-objects/splitted-by-whitespaces-string.ts
index 38c37e62..4ba6f3dc 100644
--- a/src/app/shared/value-objects/splitted-by-whitespaces-string.ts
+++ b/src/app/shared/value-objects/splitted-by-whitespaces-string.ts
@@ -15,4 +15,8 @@ export class SplittedByWhitespacesString {
this.value = '';
}
+
+ toString(): string {
+ return this.value;
+ }
}
diff --git a/src/index.html b/src/index.html
index 9cc238e4..0b79fc6f 100644
--- a/src/index.html
+++ b/src/index.html
@@ -34,7 +34,10 @@
Tech.Interview
Kazakhstan, Almaty
-
2024, © maximgorbatyuk
+
+ 2024,
+
+