1+ /*
2+ * Project Name : Visual Python
3+ * Description : GUI-based Python code generator
4+ * File Name : figure.js
5+ * Author : Black Logic
6+ * Note : Library Component
7+ * License : GNU GPLv3 with Visual Python special exception
8+ * Date : 2021. 11. 18
9+ * Change Date :
10+ */
11+
12+ //============================================================================
13+ // [CLASS] Figure
14+ //============================================================================
15+ define ( [
16+ 'vp_base/js/com/component/LibraryComponent' ,
17+ 'vp_base/js/com/component/VarSelector'
18+ ] , function ( LibraryComponent , VarSelector ) {
19+ /**
20+ * Figure
21+ */
22+ class Figure extends LibraryComponent {
23+ _init ( ) {
24+ super . _init ( ) ;
25+
26+ this . state = {
27+ subplotsRows :'' ,
28+ subplotsCols :'' ,
29+ o0 :'figure' ,
30+ figsize :'' ,
31+ ...this . state
32+ }
33+
34+ this . package = {
35+ name :'plt.subplots()' ,
36+ code :'${o0}, ax = plt.subplots(${subplotsRows}, ${subplotsCols}${v})' ,
37+ input :[
38+ {
39+ name :'subplotsRows' ,
40+ type :'int' ,
41+ label :'Number of Rows' ,
42+ component :'input_single'
43+ } ,
44+ {
45+ name :'subplotsCols' ,
46+ type :'int' ,
47+ label :'Number of Columns' ,
48+ component :'input_single'
49+ }
50+ ] ,
51+ output :[
52+ {
53+ name :'o0' ,
54+ type :'var' ,
55+ label :'Figure Variable' ,
56+ component :'input_single' ,
57+ required :true
58+ }
59+ ] ,
60+ variable :[
61+ {
62+ name :'figsize' ,
63+ type :'var'
64+ }
65+ ]
66+ } ;
67+ }
68+
69+ _bindEvent ( ) {
70+ super . _bindEvent ( ) ;
71+
72+ var that = this ;
73+
74+ // add subplot box on changing subplots row/column
75+ $ ( this . wrapSelector ( '#subplotsRows' ) ) . change ( function ( ) {
76+ var row = $ ( that . wrapSelector ( '#subplotsRows' ) ) . val ( ) * 1 ;
77+ var col = $ ( that . wrapSelector ( '#subplotsCols' ) ) . val ( ) * 1 ;
78+ // nothing entered, set 1
79+ if ( row == 0 ) row = 1 ;
80+ if ( col == 0 ) col = 1 ;
81+
82+ $ ( that . wrapSelector ( '#subplotsRowsRange' ) ) . val ( row ) ;
83+
84+ $ ( that . wrapSelector ( '#vp_subplotsGrid' ) ) . attr ( 'data-row' , row ) ;
85+ $ ( that . wrapSelector ( '#vp_subplotsGrid' ) ) . css ( 'height' , 80 * row + 'px' ) ;
86+ $ ( that . wrapSelector ( '#vp_subplotsGrid' ) ) . css ( 'grid-template-rows' , 'repeat(' + row + ', 1fr)' ) ;
87+
88+ $ ( that . wrapSelector ( '#vp_subplotsGrid' ) ) . html ( '' ) ;
89+
90+ for ( var i = 0 ; i < row * col ; i ++ ) {
91+ var div = document . createElement ( 'div' ) ;
92+ var r = parseInt ( i / col ) ;
93+ var c = i % col ;
94+ $ ( div ) . attr ( {
95+ 'class' :'grid-item' ,
96+ 'data-idx' :i ,
97+ 'data-row' :r ,
98+ 'data-col' :c
99+ } ) ;
100+
101+ var position = i ;
102+ if ( row > 1 && col > 1 ) {
103+ position = r + ', ' + c ;
104+ }
105+ $ ( div ) . text ( '[' + position + ']' ) ;
106+ $ ( div ) . click ( function ( evt ) { that . subplotBoxClickHandler ( that , evt ) ; } ) ;
107+ $ ( that . wrapSelector ( '#vp_subplotsGrid' ) ) . append ( div ) ;
108+ }
109+
110+ // initialize subplot value
111+ that . selectedIdx = '0' ;
112+ if ( row > 1 && col > 1 ) {
113+ that . selectedIdx = '0, 0' ;
114+ }
115+ that . subplotOption = [ ] ;
116+ // add space for subplot
117+ for ( var i = 0 ; i < row * col ; i ++ ) {
118+ that . subplotOption . push ( { idx :i } ) ;
119+ }
120+ } ) ;
121+
122+ $ ( this . wrapSelector ( '#subplotsCols' ) ) . change ( function ( ) {
123+ var row = $ ( that . wrapSelector ( '#subplotsRows' ) ) . val ( ) * 1 ;
124+ var col = $ ( that . wrapSelector ( '#subplotsCols' ) ) . val ( ) * 1 ;
125+ // nothing entered, set 1
126+ if ( row == 0 ) row = 1 ;
127+ if ( col == 0 ) col = 1 ;
128+
129+ $ ( that . wrapSelector ( '#subplotsColsRange' ) ) . val ( col ) ;
130+
131+ $ ( that . wrapSelector ( '#vp_subplotsGrid' ) ) . attr ( 'data-col' , col ) ;
132+ $ ( that . wrapSelector ( '#vp_subplotsGrid' ) ) . css ( 'width' , 80 * col + 'px' ) ;
133+ $ ( that . wrapSelector ( '#vp_subplotsGrid' ) ) . css ( 'grid-template-columns' , 'repeat(' + col + ', 1fr)' ) ;
134+
135+ $ ( that . wrapSelector ( '#vp_subplotsGrid' ) ) . html ( '' ) ;
136+
137+ for ( var i = 0 ; i < row * col ; i ++ ) {
138+ var div = document . createElement ( 'div' ) ;
139+ var r = parseInt ( i / col ) ;
140+ var c = i % col ;
141+ $ ( div ) . attr ( {
142+ 'class' :'grid-item' ,
143+ 'data-idx' :i ,
144+ 'data-row' :r ,
145+ 'data-col' :c
146+ } ) ;
147+
148+ var position = i ;
149+ if ( row > 1 && col > 1 ) {
150+ position = r + ', ' + c ;
151+ }
152+ $ ( div ) . text ( '[' + position + ']' ) ;
153+ $ ( div ) . click ( function ( evt ) { that . subplotBoxClickHandler ( that , evt ) ; } ) ;
154+ $ ( that . wrapSelector ( '#vp_subplotsGrid' ) ) . append ( div ) ;
155+ }
156+
157+ // initialize subplot value
158+ that . selectedIdx = '0' ;
159+ if ( row > 1 && col > 1 ) {
160+ that . selectedIdx = '0, 0' ;
161+ }
162+ that . subplotOption = [ ] ;
163+ // add space for subplot
164+ for ( var i = 0 ; i < row * col ; i ++ ) {
165+ that . subplotOption . push ( { idx :i } ) ;
166+ }
167+ } ) ;
168+
169+
170+ // subplot 위치 버튼 클릭 시 해당 subplot의 옵션 설정을 위해 다음 페이지로 이동
171+ // 참고 : vpContainer.js > tabPageShow($(this).index());
172+ // - #vp_subplot span[data-caption-id="vp_functionDetail"]
173+ // - #vp_subplotOptional span[data-caption-id="vp_functionDetail"]
174+ $ ( this . wrapSelector ( '#vp_subplotsGrid div' ) ) . click ( function ( evt ) {
175+ that . subplotBoxClickHandler ( that , evt ) ;
176+ } ) ;
177+
178+ // range 변경 시 값 표시
179+ $ ( this . wrapSelector ( '#subplotsRowsRange' ) ) . change ( function ( ) {
180+ var value = $ ( this ) . val ( ) ;
181+ $ ( that . wrapSelector ( '#subplotsRows' ) ) . val ( value ) ;
182+ $ ( that . wrapSelector ( '#subplotsRows' ) ) . change ( ) ;
183+ } ) ;
184+ $ ( this . wrapSelector ( '#subplotsColsRange' ) ) . change ( function ( ) {
185+ var value = $ ( this ) . val ( ) ;
186+ $ ( that . wrapSelector ( '#subplotsCols' ) ) . val ( value ) ;
187+ $ ( that . wrapSelector ( '#subplotsCols' ) ) . change ( ) ;
188+ } ) ;
189+
190+
191+ }
192+
193+ bindCmapSelector ( ) {
194+ // 기존 cmap 선택하는 select 태그 안보이게
195+ var cmapSelector = this . wrapSelector ( '#cmap' ) ;
196+ $ ( cmapSelector ) . hide ( ) ;
197+
198+ // cmap 데이터로 팔레트 div 동적 구성
199+ this . cmap . forEach ( ctype => {
200+ var divColor = document . createElement ( 'div' ) ;
201+ $ ( divColor ) . attr ( {
202+ 'class' :'vp-plot-cmap-item' ,
203+ 'data-cmap' :ctype ,
204+ 'data-url' :'pandas/cmap/' + ctype + '.JPG' ,
205+ 'title' :ctype
206+ } ) ;
207+ $ ( divColor ) . text ( ctype ) ;
208+ // 이미지 url 바인딩
209+ var url = Jupyter . notebook . base_url + vpConst . BASE_PATH + vpConst . RESOURCE_PATH + 'pandas/cmap/' + ctype + '.JPG' ;
210+ $ ( divColor ) . css ( {
211+ 'background-image' :'url(' + url + ')'
212+ } )
213+
214+ var selectedCmap = this . wrapSelector ( '#vp_selectedCmap' ) ;
215+
216+ // 선택 이벤트 등록
217+ $ ( divColor ) . click ( function ( ) {
218+ if ( ! $ ( this ) . hasClass ( 'selected' ) ) {
219+ $ ( this ) . parent ( ) . find ( '.vp-plot-cmap-item.selected' ) . removeClass ( 'selected' ) ;
220+ $ ( this ) . addClass ( 'selected' ) ;
221+ // 선택된 cmap 이름 표시
222+ $ ( selectedCmap ) . text ( ctype ) ;
223+ // 선택된 cmap data-caption-id 변경
224+ $ ( selectedCmap ) . attr ( 'data-caption-id' , ctype ) ;
225+ // select 태그 강제 선택
226+ $ ( cmapSelector ) . val ( ctype ) . prop ( 'selected' , true ) ;
227+ }
228+ } ) ;
229+ $ ( this . wrapSelector ( '#vp_plotCmapSelector' ) ) . append ( divColor ) ;
230+ } ) ;
231+
232+ // 선택 이벤트
233+ $ ( this . wrapSelector ( '.vp-plot-cmap-wrapper' ) ) . click ( function ( ) {
234+ $ ( this ) . toggleClass ( 'open' ) ;
235+ } ) ;
236+ }
237+
238+ templateForBody ( ) {
239+ return `
240+ <div class="vp-grid-box">
241+ <div class="vp-grid-box vp-grid-col-95">
242+ <label for="o0" class="vp-orange-text">Allocate to</label>
243+ <input type="text" class="vp-input input-single" id="o0" index=0 value="fig"/>
244+ </div>
245+ <div class="vp-grid-box vp-grid-col-95">
246+ <label for="subplotsRows">Row</label>
247+ <div class="vp-grid-col-p50">
248+ <input type="range" class="subplot-range" id="subplotsRowsRange" min="0" max="30" value="1"/>
249+ <input type="number" class="vp-input s input-range number-only" id="subplotsRows" index=0 placeholder="row" oninput="this.value = this.value.replace(/[^0-9.]/g, '').replace(/(\..*)\./g, '$1');" value="1"/>
250+ </div>
251+ </div>
252+ <div class="vp-grid-box vp-grid-col-95">
253+ <label for="subplotsCols">Column</label>
254+ <div class="vp-grid-col-p50">
255+ <input type="range" class="subplot-range" id="subplotsColsRange" min="0" max="30" value="1"/>
256+ <input type="number" class="vp-input s input-range number-only" id="subplotsCols" index=1 placeholder="column" oninput="this.value = this.value.replace(/[^0-9.]/g, '').replace(/(\..*)\./g, '$1');" value="1"/>
257+ </div>
258+ </div>
259+ <div class="vp-grid-box vp-grid-col-95">
260+ <label for="figsize">Figure Size</label>
261+ <input type="text" class="vp-input input-single" id="figsize" index=0 placeholder="(width, height)"/>
262+ </div>
263+ </div>` ;
264+ }
265+ render ( ) {
266+ super . render ( ) ;
267+
268+ // add var selector
269+ var varSelector = new VarSelector ( [ 'DataFrame' , 'Series' , 'Index' ] , 'DataFrame' , false ) ;
270+ varSelector . setComponentId ( 'i0' ) ;
271+ varSelector . addClass ( 'vp-state' ) ;
272+ varSelector . setUseColumn ( true ) ;
273+ varSelector . setValue ( this . state . i0 ) ;
274+ $ ( this . wrapSelector ( '#i0' ) ) . replaceWith ( varSelector . render ( ) ) ;
275+ }
276+
277+ subplotBoxClickHandler ( that , event ) {
278+ var target = event . target ;
279+ var parent = $ ( target ) . parent ( ) ;
280+
281+ // 다음 옵션 페이지 이동
282+ var thisPageIdx = $ ( target ) . closest ( vpConst . OPTION_PAGE ) . index ( ) ;
283+ vpContainer . tabPageShow ( thisPageIdx + 1 ) ;
284+
285+ // 다음 옵션 페이지의 위치 설명란에 [0,0] 표기
286+ var row = $ ( target ) . data ( 'row' ) ;
287+ var col = $ ( target ) . data ( 'col' ) ;
288+
289+ // 표기 : row나 col이 하나라도 1이면 1차원, 그 이상이면 2차원 배열로 접근
290+ var position = $ ( target ) . data ( 'idx' ) ;
291+ var rowLen = $ ( parent ) . data ( 'row' ) * 1 ;
292+ var colLen = $ ( parent ) . data ( 'col' ) * 1 ;
293+
294+ if ( rowLen > 1 && colLen > 1 ) { // TODO: parent().data-row / data-col 받아와야댐
295+ position = row + ', ' + col ;
296+ }
297+ // 선택한 서브플롯 인덱스 설정
298+ that . selectedIdx = position + '' ;
299+
300+ $ ( that . wrapSelector ( 'span[data-caption-id="pageTitle"]' ) ) [ 1 ] . innerText = 'Subplot [' + position + '] Setting' ;
301+ $ ( that . wrapSelector ( 'span[data-caption-id="pageTitle"]' ) ) [ 2 ] . innerText = 'Subplot [' + position + '] Add Chart' ;
302+ }
303+ }
304+
305+ return Figure ;
306+ } ) ;