woals
08-19
240819 권민수 학부모 대시보드 문제 지표 점수 시각화 적용
@75c53f5ef44616ed7c00201a267a1a290e620f3e
--- client/views/pages/parents/ColumnLineChart.vue
+++ client/views/pages/parents/ColumnLineChart.vue
... | ... | @@ -1,206 +1,237 @@ |
1 | 1 |
<template> |
2 |
- <div ref="ColumnLineChart" style="width: 500px; height: 500px;"></div> |
|
3 |
- </template> |
|
4 |
- |
|
5 |
- <script> |
|
6 |
- import * as am5 from "@amcharts/amcharts5"; |
|
7 |
- import * as am5xy from "@amcharts/amcharts5/xy"; |
|
8 |
- import am5themes_Animated from "@amcharts/amcharts5/themes/Animated"; |
|
9 |
- |
|
10 |
- export default { |
|
11 |
- name: "ColumnLineChart", |
|
12 |
- mounted() { |
|
13 |
- this.createChart(); |
|
2 |
+ <div ref="ColumnLineChart" style="width: 500px; height: 500px;"></div> |
|
3 |
+</template> |
|
4 |
+ |
|
5 |
+<script> |
|
6 |
+import * as am5 from "@amcharts/amcharts5"; |
|
7 |
+import * as am5xy from "@amcharts/amcharts5/xy"; |
|
8 |
+import am5themes_Animated from "@amcharts/amcharts5/themes/Animated"; |
|
9 |
+import axios from "axios"; |
|
10 |
+ |
|
11 |
+export default { |
|
12 |
+ name: "ColumnLineChart", |
|
13 |
+ |
|
14 |
+ data() { |
|
15 |
+ return { |
|
16 |
+ chartData: [], |
|
17 |
+ currentStdId: "1" |
|
18 |
+ }; |
|
19 |
+ }, |
|
20 |
+ |
|
21 |
+ mounted() { |
|
22 |
+ this.getMtrScoreData(); |
|
23 |
+ }, |
|
24 |
+ |
|
25 |
+ methods: { |
|
26 |
+ // 학생의 문제 지표 점수 데이터 가져오기 |
|
27 |
+ getMtrScoreData() { |
|
28 |
+ const vm = this; |
|
29 |
+ axios.post('/dashboard/mtrScoreData.json', { |
|
30 |
+ std_id: vm.currentStdId |
|
31 |
+ }) |
|
32 |
+ .then(response => { |
|
33 |
+ if (response.data) { |
|
34 |
+ // 데이터를 날짜순으로 정렬 |
|
35 |
+ vm.chartData = response.data.sort( |
|
36 |
+ (a, b) => new Date(a.learnedDate) - new Date(b.learnedDate) |
|
37 |
+ ); |
|
38 |
+ vm.createChart(); |
|
39 |
+ } else { |
|
40 |
+ console.error("mtrScoreList가 응답 데이터에 없습니다."); |
|
41 |
+ } |
|
42 |
+ }) |
|
43 |
+ .catch(error => { |
|
44 |
+ console.error('문제 지표 점수 데이터를 가져오는 중 오류 발생:', error); |
|
45 |
+ }); |
|
14 | 46 |
}, |
15 |
- methods: { |
|
16 |
- createChart() { |
|
17 |
- // Initialize root |
|
18 |
- const root = am5.Root.new(this.$refs.ColumnLineChart); |
|
19 |
- |
|
20 |
- // Apply themes |
|
21 |
- const myTheme = am5.Theme.new(root); |
|
22 |
- myTheme.rule("Grid", ["base"]).setAll({ |
|
23 |
- strokeOpacity: 0.1, |
|
47 |
+ |
|
48 |
+ createChart() { |
|
49 |
+ // Initialize root |
|
50 |
+ const root = am5.Root.new(this.$refs.ColumnLineChart); |
|
51 |
+ |
|
52 |
+ // Apply themes |
|
53 |
+ root.setThemes([ |
|
54 |
+ am5themes_Animated.new(root), |
|
55 |
+ ]); |
|
56 |
+ |
|
57 |
+ // Create chart |
|
58 |
+ const chart = root.container.children.push( |
|
59 |
+ am5xy.XYChart.new(root, { |
|
60 |
+ panX: false, |
|
61 |
+ panY: false, |
|
62 |
+ wheelX: "panX", |
|
63 |
+ wheelY: "zoomX", |
|
64 |
+ paddingLeft: 0, |
|
65 |
+ layout: root.verticalLayout |
|
66 |
+ }) |
|
67 |
+ ); |
|
68 |
+ |
|
69 |
+ // Create axes |
|
70 |
+ const xRenderer = am5xy.AxisRendererX.new(root, { |
|
71 |
+ minorGridEnabled: true, |
|
72 |
+ minGridDistance: 60 |
|
73 |
+ }); |
|
74 |
+ |
|
75 |
+ const xAxis = chart.xAxes.push( |
|
76 |
+ am5xy.CategoryAxis.new(root, { |
|
77 |
+ categoryField: "learnedDate", |
|
78 |
+ renderer: xRenderer, |
|
79 |
+ tooltip: am5.Tooltip.new(root, {}) |
|
80 |
+ }) |
|
81 |
+ ); |
|
82 |
+ |
|
83 |
+ xRenderer.grid.template.setAll({ |
|
84 |
+ location: 1 |
|
85 |
+ }); |
|
86 |
+ |
|
87 |
+ xAxis.data.setAll(this.chartData); |
|
88 |
+ |
|
89 |
+ let yAxis = chart.yAxes.push( |
|
90 |
+ am5xy.ValueAxis.new(root, { |
|
91 |
+ min: 0, |
|
92 |
+ extraMax: 0.1, |
|
93 |
+ renderer: am5xy.AxisRendererY.new(root, { |
|
94 |
+ strokeOpacity: 0.1 |
|
95 |
+ }) |
|
96 |
+ }) |
|
97 |
+ ); |
|
98 |
+ |
|
99 |
+ // 이해 점수 막대 그래프 |
|
100 |
+ let series1 = chart.series.push( |
|
101 |
+ am5xy.ColumnSeries.new(root, { |
|
102 |
+ name: "이해 점수", |
|
103 |
+ xAxis: xAxis, |
|
104 |
+ yAxis: yAxis, |
|
105 |
+ valueYField: "totalMtr01Score", |
|
106 |
+ categoryXField: "learnedDate", |
|
107 |
+ tooltip: am5.Tooltip.new(root, { |
|
108 |
+ pointerOrientation: "horizontal", |
|
109 |
+ labelText: "{name} in {categoryX}: {valueY}" |
|
110 |
+ }) |
|
111 |
+ }) |
|
112 |
+ ); |
|
113 |
+ |
|
114 |
+ // 파스텔 블루 |
|
115 |
+ series1.columns.template.setAll({ |
|
116 |
+ fill: am5.color(0x87CEEB), |
|
117 |
+ stroke: am5.color(0x87CEEB), |
|
118 |
+ tooltipY: am5.percent(10), |
|
119 |
+ }); |
|
120 |
+ |
|
121 |
+ series1.data.setAll(this.chartData); |
|
122 |
+ |
|
123 |
+ // 표현 점수 막대 그래프 |
|
124 |
+ let series2 = chart.series.push( |
|
125 |
+ am5xy.ColumnSeries.new(root, { |
|
126 |
+ name: "표현 점수", |
|
127 |
+ xAxis: xAxis, |
|
128 |
+ yAxis: yAxis, |
|
129 |
+ valueYField: "totalMtr02Score", |
|
130 |
+ categoryXField: "learnedDate", |
|
131 |
+ tooltip: am5.Tooltip.new(root, { |
|
132 |
+ pointerOrientation: "horizontal", |
|
133 |
+ labelText: "{name} in {categoryX}: {valueY}" |
|
134 |
+ }) |
|
135 |
+ }) |
|
136 |
+ ); |
|
137 |
+ |
|
138 |
+ // 파스텔 핑크 |
|
139 |
+ series2.columns.template.setAll({ |
|
140 |
+ fill: am5.color(0xFFB6C1), |
|
141 |
+ stroke: am5.color(0xFFB6C1), |
|
142 |
+ tooltipY: am5.percent(10), |
|
143 |
+ }); |
|
144 |
+ |
|
145 |
+ series2.data.setAll(this.chartData); |
|
146 |
+ |
|
147 |
+ // 이해 점수 변화 꺾은선 그래프 |
|
148 |
+ const lineSeries1 = chart.series.push( |
|
149 |
+ am5xy.LineSeries.new(root, { |
|
150 |
+ name: "이해 점수 변화", |
|
151 |
+ xAxis: xAxis, |
|
152 |
+ yAxis: yAxis, |
|
153 |
+ valueYField: "totalMtr01Score", |
|
154 |
+ categoryXField: "learnedDate", |
|
155 |
+ stroke: am5.color(0xADD8E6), // 라이트 블루로 설정 |
|
156 |
+ tooltip: am5.Tooltip.new(root, { |
|
157 |
+ pointerOrientation: "horizontal", |
|
158 |
+ labelText: "{name} on {categoryX}: {valueY}" |
|
159 |
+ }) |
|
160 |
+ }) |
|
161 |
+ ); |
|
162 |
+ |
|
163 |
+ lineSeries1.strokes.template.setAll({ |
|
164 |
+ strokeWidth: 3, |
|
165 |
+ stroke: am5.color(0xADD8E6), // 다시 명시적 설정 |
|
166 |
+ }); |
|
167 |
+ |
|
168 |
+ lineSeries1.bullets.push(function () { |
|
169 |
+ return am5.Bullet.new(root, { |
|
170 |
+ sprite: am5.Circle.new(root, { |
|
171 |
+ radius: 5, |
|
172 |
+ fill: am5.color(0xADD8E6), // 라이트 블루로 설정 |
|
173 |
+ strokeWidth: 3, |
|
174 |
+ stroke: am5.color(0xADD8E6) |
|
175 |
+ }) |
|
24 | 176 |
}); |
25 |
- root.setThemes([ |
|
26 |
- am5themes_Animated.new(root), |
|
27 |
- myTheme, |
|
28 |
- ]); |
|
29 |
- |
|
30 |
- // Create chart |
|
31 |
- let chart = root.container.children.push( |
|
32 |
- am5xy.XYChart.new(root, { |
|
33 |
- panX: false, |
|
34 |
- panY: false, |
|
35 |
- wheelX: "panX", |
|
36 |
- wheelY: "zoomX", |
|
37 |
- paddingLeft: 0, |
|
38 |
- layout: root.verticalLayout |
|
39 |
- }) |
|
40 |
-); |
|
177 |
+ }); |
|
41 | 178 |
|
42 |
-// Add scrollbar |
|
43 |
-// https://www.amcharts.com/docs/v5/charts/xy-chart/scrollbars/ |
|
44 |
-// chart.set( |
|
45 |
-// "scrollbarX", |
|
46 |
-// am5.Scrollbar.new(root, { |
|
47 |
-// orientation: "horizontal" |
|
48 |
-// }) |
|
49 |
-// ); |
|
179 |
+ lineSeries1.data.setAll(this.chartData); |
|
50 | 180 |
|
51 |
-let data = [ |
|
52 |
- { |
|
53 |
- year: "2016", |
|
54 |
- income: 23.5, |
|
55 |
- expenses: 21.1 |
|
56 |
- }, |
|
57 |
- { |
|
58 |
- year: "2017", |
|
59 |
- income: 26.2, |
|
60 |
- expenses: 30.5 |
|
61 |
- }, |
|
62 |
- { |
|
63 |
- year: "2018", |
|
64 |
- income: 30.1, |
|
65 |
- expenses: 34.9 |
|
66 |
- }, |
|
67 |
- { |
|
68 |
- year: "2019", |
|
69 |
- income: 29.5, |
|
70 |
- expenses: 31.1 |
|
71 |
- }, |
|
72 |
- { |
|
73 |
- year: "2020", |
|
74 |
- income: 30.6, |
|
75 |
- expenses: 28.2, |
|
76 |
- strokeSettings: { |
|
77 |
- stroke: chart.get("colors").getIndex(1), |
|
78 |
- strokeWidth: 3, |
|
79 |
- strokeDasharray: [5, 5] |
|
80 |
- } |
|
81 |
- }, |
|
82 |
- { |
|
83 |
- year: "2021", |
|
84 |
- income: 34.1, |
|
85 |
- expenses: 32.9, |
|
86 |
- columnSettings: { |
|
87 |
- strokeWidth: 1, |
|
88 |
- strokeDasharray: [5], |
|
89 |
- fillOpacity: 0.2 |
|
181 |
+ // 표현 점수 변화 꺾은선 그래프 |
|
182 |
+ const lineSeries2 = chart.series.push( |
|
183 |
+ am5xy.LineSeries.new(root, { |
|
184 |
+ name: "표현 점수 변화", |
|
185 |
+ xAxis: xAxis, |
|
186 |
+ yAxis: yAxis, |
|
187 |
+ valueYField: "totalMtr02Score", |
|
188 |
+ categoryXField: "learnedDate", |
|
189 |
+ stroke: am5.color(0xFF69B4), // 핫 핑크로 설정 |
|
190 |
+ tooltip: am5.Tooltip.new(root, { |
|
191 |
+ pointerOrientation: "horizontal", |
|
192 |
+ labelText: "{name} on {categoryX}: {valueY}" |
|
193 |
+ }) |
|
194 |
+ }) |
|
195 |
+ ); |
|
196 |
+ |
|
197 |
+ lineSeries2.strokes.template.setAll({ |
|
198 |
+ strokeWidth: 3, |
|
199 |
+ stroke: am5.color(0xFF69B4), // 다시 명시적 설정 |
|
200 |
+ }); |
|
201 |
+ |
|
202 |
+ lineSeries2.bullets.push(function () { |
|
203 |
+ return am5.Bullet.new(root, { |
|
204 |
+ sprite: am5.Circle.new(root, { |
|
205 |
+ radius: 5, |
|
206 |
+ fill: am5.color(0xFF69B4), // 핫 핑크로 설정 |
|
207 |
+ strokeWidth: 3, |
|
208 |
+ stroke: am5.color(0xFF69B4) |
|
209 |
+ }) |
|
210 |
+ }); |
|
211 |
+ }); |
|
212 |
+ |
|
213 |
+ lineSeries2.data.setAll(this.chartData); |
|
214 |
+ |
|
215 |
+ chart.set("cursor", am5xy.XYCursor.new(root, {})); |
|
216 |
+ |
|
217 |
+ let legend = chart.children.push( |
|
218 |
+ am5.Legend.new(root, { |
|
219 |
+ centerX: am5.p50, |
|
220 |
+ x: am5.p50 |
|
221 |
+ }) |
|
222 |
+ ); |
|
223 |
+ legend.data.setAll(chart.series.values); |
|
224 |
+ |
|
225 |
+ chart.appear(1000, 100); |
|
226 |
+ series1.appear(); |
|
227 |
+ series2.appear(); |
|
228 |
+ lineSeries1.appear(); |
|
229 |
+ lineSeries2.appear(); |
|
90 | 230 |
}, |
91 |
- info: "(projection)" |
|
92 |
- } |
|
93 |
-]; |
|
231 |
+ }, |
|
232 |
+}; |
|
233 |
+</script> |
|
94 | 234 |
|
95 |
-// Create axes |
|
96 |
-// https://www.amcharts.com/docs/v5/charts/xy-chart/axes/ |
|
97 |
-let xRenderer = am5xy.AxisRendererX.new(root, { |
|
98 |
- minorGridEnabled: true, |
|
99 |
- minGridDistance: 60 |
|
100 |
-}); |
|
101 |
-let xAxis = chart.xAxes.push( |
|
102 |
- am5xy.CategoryAxis.new(root, { |
|
103 |
- categoryField: "year", |
|
104 |
- renderer: xRenderer, |
|
105 |
- tooltip: am5.Tooltip.new(root, {}) |
|
106 |
- }) |
|
107 |
-); |
|
108 |
-xRenderer.grid.template.setAll({ |
|
109 |
- location: 1 |
|
110 |
-}) |
|
111 |
- |
|
112 |
-xAxis.data.setAll(data); |
|
113 |
- |
|
114 |
-let yAxis = chart.yAxes.push( |
|
115 |
- am5xy.ValueAxis.new(root, { |
|
116 |
- min: 0, |
|
117 |
- extraMax: 0.1, |
|
118 |
- renderer: am5xy.AxisRendererY.new(root, { |
|
119 |
- strokeOpacity: 0.1 |
|
120 |
- }) |
|
121 |
- }) |
|
122 |
-); |
|
123 |
- |
|
124 |
- |
|
125 |
-// Add series |
|
126 |
-// https://www.amcharts.com/docs/v5/charts/xy-chart/series/ |
|
127 |
- |
|
128 |
-let series1 = chart.series.push( |
|
129 |
- am5xy.ColumnSeries.new(root, { |
|
130 |
- name: "Income", |
|
131 |
- xAxis: xAxis, |
|
132 |
- yAxis: yAxis, |
|
133 |
- valueYField: "income", |
|
134 |
- categoryXField: "year", |
|
135 |
- tooltip: am5.Tooltip.new(root, { |
|
136 |
- pointerOrientation: "horizontal", |
|
137 |
- labelText: "{name} in {categoryX}: {valueY} {info}" |
|
138 |
- }) |
|
139 |
- }) |
|
140 |
-); |
|
141 |
- |
|
142 |
-series1.columns.template.setAll({ |
|
143 |
- tooltipY: am5.percent(10), |
|
144 |
- templateField: "columnSettings" |
|
145 |
-}); |
|
146 |
- |
|
147 |
-series1.data.setAll(data); |
|
148 |
- |
|
149 |
-let series2 = chart.series.push( |
|
150 |
- am5xy.LineSeries.new(root, { |
|
151 |
- name: "Expenses", |
|
152 |
- xAxis: xAxis, |
|
153 |
- yAxis: yAxis, |
|
154 |
- valueYField: "expenses", |
|
155 |
- categoryXField: "year", |
|
156 |
- tooltip: am5.Tooltip.new(root, { |
|
157 |
- pointerOrientation: "horizontal", |
|
158 |
- labelText: "{name} in {categoryX}: {valueY} {info}" |
|
159 |
- }) |
|
160 |
- }) |
|
161 |
-); |
|
162 |
- |
|
163 |
-series2.strokes.template.setAll({ |
|
164 |
- strokeWidth: 3, |
|
165 |
- templateField: "strokeSettings" |
|
166 |
-}); |
|
167 |
- |
|
168 |
- |
|
169 |
-series2.data.setAll(data); |
|
170 |
- |
|
171 |
-series2.bullets.push(function () { |
|
172 |
- return am5.Bullet.new(root, { |
|
173 |
- sprite: am5.Circle.new(root, { |
|
174 |
- strokeWidth: 3, |
|
175 |
- stroke: series2.get("stroke"), |
|
176 |
- radius: 5, |
|
177 |
- fill: root.interfaceColors.get("background") |
|
178 |
- }) |
|
179 |
- }); |
|
180 |
-}); |
|
181 |
- |
|
182 |
-chart.set("cursor", am5xy.XYCursor.new(root, {})); |
|
183 |
- |
|
184 |
-// Add legend |
|
185 |
-// https://www.amcharts.com/docs/v5/charts/xy-chart/legend-xy-series/ |
|
186 |
-let legend = chart.children.push( |
|
187 |
- am5.Legend.new(root, { |
|
188 |
- centerX: am5.p50, |
|
189 |
- x: am5.p50 |
|
190 |
- }) |
|
191 |
-); |
|
192 |
-legend.data.setAll(chart.series.values); |
|
193 |
- |
|
194 |
-// Make stuff animate on load |
|
195 |
-// https://www.amcharts.com/docs/v5/concepts/animations/ |
|
196 |
-chart.appear(1000, 100); |
|
197 |
-series1.appear(); |
|
198 |
- }, |
|
199 |
- }, |
|
200 |
- }; |
|
201 |
- </script> |
|
202 |
- |
|
203 |
- <style scoped> |
|
204 |
- /* Add necessary styles here */ |
|
205 |
- </style> |
|
206 |
-(파일 끝에 줄바꿈 문자 없음) |
|
235 |
+<style scoped> |
|
236 |
+/* Add necessary styles here */ |
|
237 |
+</style> |
--- client/views/pages/parents/Dounutchart.vue
+++ client/views/pages/parents/Dounutchart.vue
... | ... | @@ -15,7 +15,9 @@ |
15 | 15 |
return { |
16 | 16 |
chartData: {}, |
17 | 17 |
currentDate: "2024-08-14", |
18 |
- currentStdId: "1" |
|
18 |
+ currentStdId: "1", |
|
19 |
+ root: null, |
|
20 |
+ chart: null |
|
19 | 21 |
}; |
20 | 22 |
}, |
21 | 23 |
|
... | ... | @@ -42,6 +44,11 @@ |
42 | 44 |
}, |
43 | 45 |
|
44 | 46 |
createChart() { |
47 |
+ // 차트 중복 생성 방지 |
|
48 |
+ if (this.root) { |
|
49 |
+ this.root.dispose(); |
|
50 |
+ } |
|
51 |
+ |
|
45 | 52 |
// Initialize root |
46 | 53 |
const root = am5.Root.new(this.$refs.Dounutchart); |
47 | 54 |
|
... | ... | @@ -118,7 +125,7 @@ |
118 | 125 |
series.appear(1000, 100); |
119 | 126 |
} |
120 | 127 |
} |
121 |
- |
|
128 |
+ |
|
122 | 129 |
}; |
123 | 130 |
</script> |
124 | 131 |
|
Add a comment
Delete comment
Once you delete this comment, you won't be able to recover it. Are you sure you want to delete this comment?