statosio 0.3.4

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.
@@ -0,0 +1,3 @@
1
+ {
2
+ "data": [ { "name": "Neelix", "mobile": "25", "desktop": "50" }, { "name": "Data", "mobile": "51", "desktop": "87" }, { "name": "Jake Sisko", "mobile": "49", "desktop": "78" }, { "name": "Spock", "mobile": "7", "desktop": "60" }, { "name": "Montgomery Scott", "mobile": "11", "desktop": "35" }, { "name": "Nyota Uhuru", "mobile": "n/a", "desktop": "n/a" }, { "name": "Chakotay", "mobile": "24", "desktop": "77" }, { "name": "Beverly Crusher", "mobile": "13", "desktop": "45" }, { "name": "Hikaru Sulu", "mobile": "41", "desktop": "82" }, { "name": "William T. Riker", "mobile": "48", "desktop": "97" }, { "name": "Natasha Yar", "mobile": "11", "desktop": "60" }, { "name": "Kathryn Janeway", "mobile": "48", "desktop": "89" }, { "name": "Deanna Troi", "mobile": "33", "desktop": "39" }, { "name": "Quark", "mobile": "47", "desktop": "64" }, { "name": "Benjamin Sisko", "mobile": "n/a", "desktop": "n/a" }, { "name": "T'Pol", "mobile": "30", "desktop": "45" }, { "name": "Tuvok", "mobile": "17", "desktop": "50" }, { "name": "Worf", "mobile": "26", "desktop": "41" }, { "name": "B'Elanna Torres", "mobile": "10", "desktop": "63" }, { "name": "Malcolm Reed", "mobile": "14", "desktop": "55" }, { "name": "Kira Nerys", "mobile": "51", "desktop": "70" }, { "name": "Tom Paris", "mobile": "42", "desktop": "69" }, { "name": "Charles Tucker III", "mobile": "46", "desktop": "74" }, { "name": "Jonathan Archer", "mobile": "22", "desktop": "49" } ]
3
+ }
@@ -0,0 +1,30 @@
1
+ <!DOCTYPE html>
2
+ <head>
3
+ <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
4
+ <meta content="utf-8" http-equiv="encoding">
5
+ <style>
6
+ body {background-color: green}
7
+ * {
8
+ margin: 0;
9
+ padding: 0;
10
+ }
11
+ </style>
12
+ <script>
13
+ <--d3-->
14
+ </script>
15
+ <script>
16
+ <--statosio-->
17
+ </script>
18
+ <script>var dataset=<--dataset-->
19
+ </script>
20
+ </head>
21
+ <body>
22
+ <script>
23
+ d3.statosio(
24
+ dataset['data'],
25
+ '<--x-->',
26
+ <--y-->,
27
+ <--options-->
28
+ )
29
+ </script>
30
+ </body>
@@ -0,0 +1,140 @@
1
+ class Boilerplate
2
+ def initialize
3
+ @values = nil
4
+ @markers = {
5
+ dataset: {
6
+ sub: "<--dataset-->",
7
+ value: nil
8
+ },
9
+ x: {
10
+ sub: "<--x-->",
11
+ value: nil
12
+ },
13
+ y: {
14
+ sub: "<--y-->",
15
+ value: nil
16
+ },
17
+ options: {
18
+ sub: '<--options-->',
19
+ value: {}
20
+ }
21
+ }
22
+ @boilerplate = nil
23
+ @boilerplate_raw = <<'STATOSIOOOO'
24
+ <<--boilerplate-->>
25
+ STATOSIOOOO
26
+ @options_allow_list = set_options_allow_list()
27
+ end
28
+
29
+
30
+ def get_boilerplate_raw
31
+ @boilerplate_raw
32
+ end
33
+
34
+
35
+ def set_boilerplate
36
+ @boilerplate = @boilerplate_raw.clone
37
+ @markers.keys.each do | key |
38
+ case key
39
+ when :dataset
40
+ tmp = { data: @markers[ key ][:value] }
41
+ value = JSON.pretty_generate( tmp )
42
+ when :x
43
+ value = @markers[ key ][:value].to_s
44
+ when :y
45
+ value = @markers[ key ][:value].to_s
46
+ when :options
47
+ value = @markers[ key ][:value].to_json
48
+ end
49
+
50
+ @boilerplate.gsub!( @markers[ key ][:sub], value )
51
+ end
52
+ end
53
+
54
+
55
+ def get_boilerplate
56
+ @boilerplate
57
+ end
58
+
59
+
60
+ def get_markers
61
+ @markers
62
+ end
63
+
64
+
65
+ def set_markers_value( _values )
66
+ _values.keys.each do | key |
67
+ @markers[ key ][:value] = _values[ key ]
68
+ end
69
+ end
70
+
71
+
72
+ def set_options_allow_list
73
+ g = {
74
+ start: 'let default_values = ',
75
+ end: 'const params_create'
76
+ }
77
+
78
+ html = @boilerplate_raw
79
+ tmp = html[ html.index( g[:start] ) + g[:start].length, html.length ]
80
+ tmp = tmp[ 0, tmp.index( g[:end] ) ]
81
+ tmp = tmp
82
+ .strip!
83
+ .gsub( "'", '"' )
84
+ .split( "\n" )
85
+ .map { | ss | ss.include?( '//' ) ? ss[ 0, ss.index( '//' )] : ss }
86
+ .join()
87
+ options = JSON.parse( tmp )
88
+
89
+ keys = {
90
+ alias: [],
91
+ camel_case: [],
92
+ allow_list: []
93
+ }
94
+
95
+ options.keys.each do | lvl1 |
96
+ if options[ lvl1 ].class.to_s == 'Hash'
97
+ options[ lvl1 ].keys.each do | lvl2 |
98
+ if options[ lvl1 ][ lvl2 ].class.to_s == 'Hash'
99
+ options[ lvl1 ][ lvl2 ].keys.each do | lvl3 |
100
+ if options[ lvl1 ][ lvl2 ][ lvl3 ].class.to_s == 'Hash'
101
+ options[ lvl1 ][ lvl2 ][ lvl3 ].keys.each do | lvl4 |
102
+ if !options[ lvl1 ][ lvl2 ][ lvl3 ][ lvl4 ].nil?
103
+ keys[:alias].push( lvl1.to_s + '__' + lvl2.to_s + '__' + lvl3.to_s + '__' + lvl4.to_s )
104
+ end
105
+ end
106
+ else
107
+ if !options[ lvl1 ][ lvl2 ][ lvl3 ].nil?
108
+ keys[:alias].push( lvl1.to_s + '__' + lvl2.to_s + '__' + lvl3.to_s )
109
+ end
110
+ end
111
+ end
112
+ else
113
+ if !options[ lvl1 ][ lvl2 ].nil?
114
+ keys[:alias].push( lvl1.to_s + '__' + lvl2.to_s )
115
+ end
116
+ end
117
+ end
118
+ else
119
+ if !options[ lvl1 ].nil?
120
+ keys[:alias].push( lvl1.to_s )
121
+ end
122
+ end
123
+ end
124
+
125
+ keys[:alias].each do | key |
126
+ tmp = key
127
+ .gsub('__', '_')
128
+ .split('_')
129
+ one = tmp[ 0 ]
130
+ two = tmp
131
+ .drop(1)
132
+ .map { | d | d[ 0, 1 ].upcase + d[ 1, d.length] }
133
+ .join()
134
+ keys[:camel_case].push( one + two )
135
+ end
136
+
137
+ keys[:allow_list] = keys[:alias].concat( keys[:camel_case] )
138
+ return keys[:allow_list]
139
+ end
140
+ end
@@ -0,0 +1,854 @@
1
+ // https://statosio.com v0.1 Copyright 2020 Andreas Banholzer
2
+
3
+ d3.statosio = ( file, x_key, y_keys, optional={} ) => {
4
+ let default_values = {
5
+ 'show': {
6
+ 'title': false,
7
+ 'legend': false,
8
+ 'average': true,
9
+ 'range_y_log': false,
10
+ 'data_as_circle': false
11
+ },
12
+ 'view': {
13
+ 'title': 'Statosio Demo',
14
+ 'dom_id': 'd3_statosio',
15
+ 'margin': {
16
+ 'top': 20,
17
+ 'right': 40,
18
+ 'bottom': 100,
19
+ 'left': 60
20
+ },
21
+ 'width': {
22
+ 'outer': 600,
23
+ 'inner': null
24
+ },
25
+ 'height': {
26
+ 'outer': 300,
27
+ 'inner': null
28
+ },
29
+ 'translate': {
30
+ 'multiplicator': 1.5,
31
+ 'title': null,
32
+ 'legend': null,
33
+ 'title_and_legend': null,
34
+ 'top_global': null,
35
+ 'legend_text': null
36
+ }
37
+ },
38
+ 'data': {
39
+ 'x': {
40
+ 'key': '',
41
+ 'selectors': [],
42
+ 'text': {
43
+ 'length': 25,
44
+ 'placeholder': '...'
45
+ }
46
+ },
47
+ 'y': {
48
+ 'keys': [],
49
+ 'ticks': 5
50
+ },
51
+ 'sort': {
52
+ 'current': 'none', // none, values, names
53
+ 'by': {
54
+ 'values': 'decending', // none , ascending, decending
55
+ 'names': 'ascending' // none , ascending, decending
56
+ },
57
+ 'selection': 'none' // none, start, end
58
+ },
59
+ 'legend': {
60
+ 'names': {
61
+ 'keys': null,
62
+ 'titleized': null
63
+ },
64
+ 'text': {
65
+ 'separater': '_'
66
+ }
67
+ }
68
+ },
69
+ 'style': {
70
+ 'color': {
71
+ 'average': '#000000',
72
+ 'canvas_background': 'white',
73
+ 'font': '#000000',
74
+ 'legends': [ '#5186EC','#D95040', '#F2BD42' ],
75
+ 'gridline': '#E5E5E5',
76
+ 'selectors': {
77
+ 'chart': [ '#EE752F', '#5186EC' ],
78
+ 'text': [ '#000000', '#000000' ]
79
+ }
80
+ },
81
+ 'font': {
82
+ 'size': {
83
+ 'text': 10,
84
+ 'title': 18
85
+ },
86
+ 'family': 'arial',
87
+ 'format': {
88
+ 'selectors': [ 'bold', 'normal' ]
89
+ }
90
+ },
91
+ 'stroke': {
92
+ 'average': 2,
93
+ 'gridline': 2
94
+ },
95
+ 'legend' : {
96
+ 'rect_size': {
97
+ 'full': 16,
98
+ 'half': null
99
+ },
100
+ 'padding': {
101
+ 'before': null,
102
+ 'after': null
103
+ }
104
+ },
105
+ 'other': {
106
+ 'legend_padding_text_after': null,
107
+ 'legend_padding_text_before': null,
108
+ 'circle_chart_radius': 4,
109
+ 'range_x_text_rotation': -45
110
+ }
111
+ }
112
+ }
113
+
114
+
115
+ const params_create = ( _default_values, _user_x_key, _user_y_keys, _user_optional ) => {
116
+ const prepare_params_access_list = ( obj, _user_x_key, _user_y_keys ) => {
117
+ const key_finding = ( obj, keys ) => {
118
+ let val = ''
119
+ let str = keys.join( '__' )
120
+ allow = false
121
+ switch( keys.length) {
122
+ case 1:
123
+ val = obj[ keys[ 0 ] ]
124
+ break;
125
+ case 2:
126
+ val = obj[ keys[ 0 ] ][ keys[ 1 ] ]
127
+ break;
128
+ case 3:
129
+ val = obj[ keys[ 0 ] ][ keys[ 1 ] ][ keys[ 2 ] ]
130
+ break;
131
+ case 4:
132
+ val = obj[ keys[ 0 ] ][ keys[ 1 ] ][ keys[ 2 ] ][ keys[ 3 ] ]
133
+ break;
134
+ }
135
+
136
+ val === null ? '' : allow = true
137
+ allow ? access['allow'].push( str ) : access['block'].push( str )
138
+ return allow
139
+ }
140
+
141
+
142
+ const detect_finding = ( obj, keys ) => {
143
+ const is_array = ( _val ) => {
144
+ return is_object( _val ) && ( _val instanceof Array )
145
+ }
146
+
147
+ const is_object = ( _val ) => {
148
+ return _val && (typeof _val === 'object' )
149
+ }
150
+
151
+ let val = null
152
+ switch( keys.length ) {
153
+ case 1:
154
+ val = obj[ keys[ 0 ] ]
155
+ break;
156
+ case 2:
157
+ val = obj[ keys[ 0 ] ][ keys[ 1 ] ]
158
+ break;
159
+ case 3:
160
+ val = obj[ keys[ 0 ] ][ keys[ 1 ] ][ keys[ 2 ] ]
161
+ break;
162
+ case 4:
163
+ val = obj[ keys[ 0 ] ][ keys[ 1 ] ][ keys[ 2 ] ][ keys[ 3 ] ]
164
+ break;
165
+ }
166
+
167
+ let result = {
168
+ 'null': null,
169
+ 'string': null,
170
+ 'array': null,
171
+ 'object': null,
172
+ }
173
+
174
+ val !== null ? result['null'] = true : result['null'] = false
175
+ typeof( val ) === 'string' ? result['string'] = true : result['string'] = false
176
+
177
+ if ( is_array( val ) ) {
178
+ result['object'] = false
179
+ }
180
+ else if ( is_object( val ) ) {
181
+ result['object'] = true
182
+ }
183
+
184
+ is_array( val ) ? result['array'] = true : result['array'] = false
185
+
186
+ return result
187
+ }
188
+
189
+
190
+ let access = { 'allow': [], 'block': [] }
191
+ let required = { 'data__x__key': _user_x_key, 'data__y__keys': _user_y_keys }
192
+ let alias = {}
193
+
194
+ Object.keys( obj ).forEach( ( key1 ) => {
195
+ let ks1 = [ key1 ]
196
+ let r = detect_finding( obj, ks1 )
197
+
198
+ if( r['object'] ) {
199
+ Object.keys( obj[ key1 ] ).forEach( ( key2 ) => {
200
+ let ks2 = [ key1, key2 ]
201
+ let r = detect_finding( obj, ks2 )
202
+ r['array'] ? key_finding( obj, ks2 ) : ''
203
+
204
+ if( r['object'] ) {
205
+ Object.keys( obj[ key1 ][ key2 ] ).forEach( ( key3 ) => {
206
+ let ks3 = [ key1, key2, key3 ]
207
+ let r = detect_finding( obj, ks3 )
208
+ r['array'] ? key_finding( obj, ks3 ) : ''
209
+
210
+ if( r['object'] ) {
211
+ Object.keys( obj[ key1 ][ key2 ][ key3 ] ).forEach( ( key4 ) => {
212
+ let ks4 = [ key1, key2, key3, key4 ]
213
+ let r = detect_finding( obj, ks4 )
214
+ r['array'] ? key_finding( obj, ks4 ) : ''
215
+
216
+ if( r['object'] ) {
217
+ } else {
218
+ key_finding( obj, ks4 )
219
+ }
220
+ } )
221
+ } else {
222
+ key_finding( obj, ks3 )
223
+ }
224
+ } )
225
+ } else {
226
+ key_finding( obj, ks2 )
227
+ }
228
+ } )
229
+ } else {
230
+ key_finding( obj, ks1 )
231
+ }
232
+ } )
233
+
234
+
235
+ Object.keys( access ).forEach( ( key1 ) => {
236
+ access[ key1 ].forEach( ( key2 ) => {
237
+ alias[ key2 ] = {}
238
+ alias[ key2 ]['type'] = key1 === 'allow' ? true : false
239
+ alias[ key2 ]['names'] = []
240
+
241
+ let tmp = key2
242
+ .split( '__' ).join( '---' )
243
+ .split( '_' ).join( '---' )
244
+ .split( '---' )
245
+
246
+ alias[ key2 ]['names'].push( key2 )
247
+
248
+ let type2 = []
249
+ tmp.forEach( ( key2, i ) => {
250
+ let word = ''
251
+ if( i != 0 ) {
252
+ word += key2.charAt( 0 ).toUpperCase()
253
+ word += key2.slice( 1 ).toLowerCase()
254
+ } else {
255
+ word = key2
256
+ }
257
+ type2.push( word )
258
+ } )
259
+ alias[ key2 ]['names'].push( type2.join( '' ) )
260
+ } )
261
+ } )
262
+
263
+ result = {
264
+ 'required': required,
265
+ 'access': access,
266
+ 'alias': alias
267
+ }
268
+ return result
269
+ }
270
+
271
+
272
+ const prepare_params_insert_user_input = ( obj, _access_list, _user_optional ) => {
273
+ const obj_insert_value = ( obj, _key_joined, insert ) => {
274
+ let keys = _key_joined.split( '__' )
275
+ switch( keys.length ) {
276
+ case 1:
277
+ obj[ keys[ 0 ] ] = insert
278
+ break;
279
+ case 2:
280
+ obj[ keys[ 0 ] ][ keys[ 1 ] ] = insert
281
+ break;
282
+ case 3:
283
+ obj[ keys[ 0 ] ][ keys[ 1 ] ][ keys[ 2 ] ] = insert
284
+ break;
285
+ case 4:
286
+ obj[ keys[ 0 ] ][ keys[ 1 ] ][ keys[ 2 ] ][ keys[ 3 ] ] = insert
287
+ break;
288
+ }
289
+ return obj
290
+ }
291
+
292
+ const required_clean_up = ( _required, key ) => {
293
+ if( key === 'data__y__keys' ) {
294
+ if( typeof _required[ key ] === 'string' ) {
295
+ if( _required[ key ].indexOf( ',' ) == -1 ) {
296
+ _required[ key ] = [ _required[ key ] ]
297
+ } else {
298
+ _required[ key ] = _required[ key ].split( ',' ).map( ( value ) => { return value.trim() } )
299
+ }
300
+ }
301
+ }
302
+ return _required
303
+ }
304
+
305
+
306
+ Object.keys( _access_list['required'] )
307
+ .forEach( ( key ) => {
308
+ _access_list['required'] = required_clean_up( _access_list['required'], key )
309
+ key == null ? console.log( 'Key "' + key + '" is empty!' ) : ''
310
+ obj = obj_insert_value( obj, key, _access_list['required'][ key ] )
311
+ } )
312
+
313
+ Object.keys( _user_optional )
314
+ .forEach( ( key1 ) => {
315
+ let search = key1
316
+ let found = {
317
+ 'key' : null,
318
+ 'add' : false,
319
+ }
320
+
321
+ Object.keys( _access_list['alias'] ).forEach( ( key2 ) => {
322
+ if( _access_list['alias'][ key2 ]['names'].includes( search ) ) {
323
+ if( _access_list['alias'][ key2 ]['type'] ) {
324
+ found['key'] = _access_list['alias'][ key2 ]['names'][ 0 ] //key2
325
+ found['add'] = true
326
+ }
327
+ }
328
+ } )
329
+
330
+ if( found['add'] ) {
331
+ insert = _user_optional[ search ]
332
+ obj = obj_insert_value( obj, found['key'], insert )
333
+ }
334
+ } )
335
+ return obj
336
+ }
337
+
338
+
339
+ const prepare_params_adjust_margin_top = ( obj, multiplicator ) => {
340
+ let struct = {
341
+ 'margin_top': null,
342
+ 'title': {
343
+ 'translate': null,
344
+ 'offset': null,
345
+ 'show': null
346
+ },
347
+ 'legend': {
348
+ 'translate': null,
349
+ 'offset': null,
350
+ 'show': null
351
+ }
352
+ }
353
+
354
+ let iteration = Object.keys( struct )
355
+ .filter( ( key ) => { return !key.includes( 'margin_top' ) } )
356
+
357
+ struct['title']['offset'] = obj['style']['font']['size']['title'] * multiplicator
358
+ struct['title']['show'] = obj['show']['title']
359
+
360
+ struct['legend']['offset'] = obj['style']['font']['size']['text'] * multiplicator
361
+ struct['legend']['show'] = obj['show']['legend']
362
+
363
+ struct['margin_top'] = obj['view']['margin']['top'] * 1
364
+ struct['margin_top'] += iteration
365
+ .map( ( key ) => { return struct[ key ]['show'] ? struct[ key ]['offset'] : 0 } )
366
+ .reduce( ( a, b ) => { return a + b } )
367
+
368
+ iteration
369
+ .forEach( ( key ) => { return struct[ key ]['translate'] = -( struct['margin_top'] - struct[ key ]['offset'] ) } )
370
+
371
+ result = {
372
+ 'canvas__margin__top': struct['margin_top'],
373
+ 'canvas__translate__title': struct['title']['translate'],
374
+ 'canvas__translate__legend': struct['legend']['translate']
375
+ }
376
+ return result
377
+ }
378
+
379
+
380
+ const helper_string_titleize = ( phrase, separater ) => {
381
+ result = phrase.split( separater )
382
+ .map( ( string ) => {
383
+ let word = ''
384
+ word += string.charAt( 0 ).toUpperCase()
385
+ word += string.slice( 1 ).toLowerCase()
386
+ return word
387
+ } )
388
+ .join( ' ' )
389
+ return result
390
+ }
391
+
392
+
393
+ const prepare_params_offset_sum = ( obj ) => {
394
+ const font_width = ( font_size_text, font_family ) => {
395
+ let dom = document.createElement( 'div' )
396
+ dom.style.position = 'absolute'
397
+ dom.style.float = 'left'
398
+ dom.style.whiteSpace = 'nowrap'
399
+ dom.style.visibility = 'hidden'
400
+ dom.style.font = font_size_text + 'px ' + font_family
401
+ dom.innerHTML = this
402
+ dom = document.body.appendChild( dom )
403
+ let _font_width = dom.offsetWidth
404
+ dom.parentNode.removeChild( dom )
405
+ return _font_width
406
+ }
407
+
408
+
409
+ let legend_offset_tmp = obj['data']['legend']['names']['titleized']
410
+ .map( ( d ) => { return font_width( obj['style']['font']['size']['text'], obj['style']['font']['family'] ) } )
411
+ .map( ( d ) => { return obj['style']['legend']['rect_size']['full'] + obj['style']['legend']['rect_size']['half'] + d + obj['style']['legend']['padding']['after'] } )
412
+ legend_offset_tmp.unshift( 0 )
413
+
414
+ let result = []
415
+ result.push( 0 )
416
+ legend_offset_tmp.forEach( ( v ) => { result.push( result[ result.length - 1 ] + v ) } )
417
+ result.shift( 0 )
418
+
419
+ return result
420
+ }
421
+
422
+
423
+ let hash = null
424
+ hash = _default_values
425
+ let access = prepare_params_access_list( hash, _user_x_key, _user_y_keys )
426
+ hash = prepare_params_insert_user_input( hash, access, _user_optional )
427
+ let tmp = prepare_params_adjust_margin_top( hash, hash['view']['translate']['multiplicator'] )
428
+
429
+ hash['view']['margin']['top'] = tmp['canvas__margin__top']
430
+ hash['view']['translate']['title'] = tmp['canvas__translate__title']
431
+ hash['view']['translate']['legend'] = tmp['canvas__translate__legend']
432
+
433
+ hash['view']['width']['inner'] = hash['view']['width']['outer'] - hash['view']['margin']['left'] - hash['view']['margin']['right']
434
+ hash['view']['height']['inner'] = hash['view']['height']['outer'] - hash['view']['margin']['top'] - hash['view']['margin']['bottom']
435
+
436
+ hash['style']['legend']['rect_size']['half'] = Math.floor( hash['style']['legend']['rect_size']['full'] / 2 )
437
+ hash['view']['translate']['legend_text'] = Math.floor( ( hash['style']['legend']['rect_size']['full'] / 100 ) * 80 )
438
+
439
+ hash['style']['legend']['padding']['before'] = hash['style']['legend']['rect_size']['half']
440
+ hash['style']['legend']['padding']['after'] = hash['style']['legend']['rect_size']['full']
441
+
442
+ hash['data']['legend']['names']['keys'] = hash['data']['y']['keys']
443
+ hash['data']['legend']['names']['titleized'] = hash['data']['legend']['names']['keys'].map( ( key ) => {
444
+ return helper_string_titleize( key, hash['data']['legend']['text']['separater'] )
445
+ } )
446
+
447
+ hash['view']['translate']['title_and_legend'] = prepare_params_offset_sum( hash )
448
+ hash['view']['translate']['top_global'] = Math.floor( (
449
+ hash['view']['width']['inner'] -
450
+ hash['view']['translate']['title_and_legend'][ hash['view']['translate']['title_and_legend'].length - 1 ]
451
+ ) / 2 )
452
+
453
+ return hash
454
+ }
455
+
456
+
457
+ const data_create = ( file, obj ) => {
458
+ const prepare_data_points = ( file, obj, _data ) => {
459
+ const helper_cast_to_number = ( value ) => {
460
+ if( value === 'true' ) {
461
+ value = 1
462
+ } else if( value === true ) {
463
+ value = 1
464
+ } else if( value === 'false' ) {
465
+ value = 0
466
+ } else if( value === false ) {
467
+ value = 0
468
+ }
469
+
470
+ let r = parseInt( value )
471
+ r = Number.isNaN( r ) ? 0 : r
472
+ return r
473
+ }
474
+
475
+ let results = file
476
+ .map( ( d, i ) => {
477
+ let result = {
478
+ 'name_full': null,
479
+ 'name_trimmed': null,
480
+ 'color_chart': null,
481
+ 'color_text': null,
482
+ 'font_format': null,
483
+ 'value': null,
484
+ 'average': null
485
+ }
486
+
487
+ if( d[ obj['data']['x']['key'] ].length > obj['data']['x']['text']['length'] ) {
488
+ result['name_full'] = d[ obj['data']['x']['key'] ]
489
+ result['name_trimmed'] = result['name_full']
490
+ .substring( 0, obj['data']['x']['text']['length'] ) + obj['data']['x']['text']['placeholder']
491
+ } else {
492
+ result['name_full'] = d[ obj['data']['x']['key'] ]
493
+ result['name_trimmed'] = result['name_full']
494
+ }
495
+
496
+ switch( _data['type'] ) {
497
+ case 'stacked':
498
+ let values = []
499
+ obj['data']['y']['keys'].forEach( ( key ) => {
500
+ result[ key ] = helper_cast_to_number( d[ key ] )
501
+ values.push( result[ key ] )
502
+ } )
503
+ result['value'] = values.reduce( ( a, b ) => { return a + b } )
504
+
505
+ break;
506
+ case 'single':
507
+ result['value'] = helper_cast_to_number( d[ obj['data']['y']['keys'][ 0 ] ] )
508
+ break;
509
+ }
510
+
511
+ result['color_chart'] = obj['data']['x']['selectors']
512
+ .includes( d[ obj['data']['x']['key'] ] ) ? obj['style']['color']['selectors']['chart'][ 0 ] : obj['style']['color']['selectors']['chart'][ 1 ]
513
+ result['color_text'] = obj['data']['x']['selectors']
514
+ .includes( d[ obj['data']['x']['key'] ] ) ? obj['style']['color']['selectors']['text'][ 0 ] : obj['style']['color']['selectors']['text'][ 1 ]
515
+ result['font_format'] = obj['data']['x']['selectors']
516
+ .includes( d[ obj['data']['x']['key'] ] ) ? obj['style']['font']['format']['selectors'][ 0 ] : obj['style']['font']['format']['selectors'][ 1 ]
517
+ return result
518
+ } )
519
+ return results
520
+ }
521
+
522
+
523
+ const prepare_data_points_sort = ( obj, _data ) => {
524
+ if( obj['data']['sort']['current'] !== 'none' && [ 'values', 'names' ].includes( obj['data']['sort']['current'] ) ) {
525
+ switch( obj['data']['sort']['current'] ) {
526
+ case 'values':
527
+ let a = obj['data']['sort']['by']['values'] !== 'none'
528
+ let b = [ 'ascending', 'decending' ].includes( obj['data']['sort']['by']['values'] )
529
+ if( a && b ) {
530
+ _data.sort( ( d, e ) => { return parseFloat( d['value'] ) - parseFloat( e['value'] ) } )
531
+ switch( obj['data']['sort']['by']['values'] ) {
532
+ case 'ascending':
533
+ break;
534
+ case 'decending':
535
+ _data.reverse()
536
+ break;
537
+ }
538
+ }
539
+ break;
540
+ case 'names':
541
+ let g = obj['data']['sort']['by']['names'] !== 'none'
542
+ let h = [ 'ascending', 'decending' ].includes( obj['data']['sort']['by']['names'] )
543
+ if( g && h ) {
544
+ _data.sort( ( a, b ) => { return a['name_full'].localeCompare( b['name_full'] ) } )
545
+ switch( obj['data']['sort']['by']['names'] ) {
546
+ case 'ascending':
547
+ break;
548
+ case 'decending':
549
+ _data.reverse()
550
+ break;
551
+ }
552
+ }
553
+ break;
554
+ }
555
+ }
556
+
557
+ if( obj['data']['sort']['selection'] !== 'none' && [ 'start', 'end' ].includes( obj['data']['sort']['selection'] ) ) {
558
+ let selection = []
559
+ let rest = []
560
+ let searchs = obj['data']['x']['selectors']
561
+ searchs.forEach( ( search ) => {
562
+ let index = _data.findIndex( ( d ) => { return d['name_full'] === search } )
563
+ let item = _data[ index ]
564
+ selection.push( item )
565
+ _data.forEach( ( d ) => {
566
+ !searchs.includes( d['name_full'] ) ? rest.push( d ) : ''
567
+ } )
568
+ } )
569
+
570
+ switch( obj['data']['sort']['selection'] ) {
571
+ case 'start':
572
+ _data = selection.concat( rest )
573
+ break;
574
+ case 'end':
575
+ _data = rest.concat( selection )
576
+ break
577
+ }
578
+ }
579
+ return _data
580
+ }
581
+
582
+
583
+ const prepare_data_average = ( _data, obj ) => {
584
+ let result = null
585
+ if( obj['show']['average'] ) {
586
+ let average_tmp = _data['points'].map( ( d ) => {
587
+ let result = null
588
+ switch( _data['type'] ) {
589
+ case 'stacked':
590
+ result = obj['data']['y']['keys']
591
+ .map( ( key ) => { return d[ key ] } )
592
+ .reduce( ( a, b ) => { return a + b } )
593
+ break;
594
+ case 'single' :
595
+ result = d['value']
596
+ break;
597
+ }
598
+ return result
599
+ } )
600
+
601
+ result = average_tmp.reduce( ( a, b ) => { return a + b } ) / average_tmp.length
602
+ result = Math.round( ( result + Number.EPSILON ) * 100 ) / 100
603
+ }
604
+ return result
605
+ }
606
+
607
+
608
+ let _dataset = {}
609
+ _dataset['type'] = obj['data']['y']['keys'].length != 1 ? 'stacked' : 'single'
610
+ _dataset['view'] = obj['show']['data_as_circle'] ? 'circle' : 'bar'
611
+ _dataset['points'] = prepare_data_points( file, obj, _dataset )
612
+ _dataset['points'] = prepare_data_points_sort( obj, _dataset['points'] )
613
+ _dataset['average'] = prepare_data_average( _dataset, obj )
614
+ _dataset['points'].forEach( ( d, i ) => { _dataset['points'][ i ]['average'] = _dataset['average'] } )
615
+ return _dataset
616
+ }
617
+
618
+
619
+ const view_create = ( obj, _dataset ) => {
620
+ const prepare_view_axes = ( _data, obj ) => {
621
+ let vals = null
622
+ switch( _data['type'] ) {
623
+ case 'single':
624
+ vals = _data['points'].map( ( d ) => { return d['value'] } )
625
+ break;
626
+ case 'stacked':
627
+ vals = _data['points'].map( ( d ) => {
628
+ a = obj['data']['y']['keys'].map( ( key ) => { return d[ key ] } )
629
+ return a.reduce( ( ( accumulator, currentValue ) => { return accumulator + currentValue } ) )
630
+ } )
631
+ break;
632
+ }
633
+
634
+ const x = d3.scaleBand()
635
+ .domain( _data['points'].map( ( d ) => { return d['name_trimmed'] } ) )
636
+ .range( [ 0, obj['view']['width']['inner'] ] )
637
+
638
+ let y = null
639
+ let range_y = [ 0, Math.max( ...vals ) * 1.05 ]
640
+ if( obj['show']['range_y_log'] ) {
641
+ y = d3.scaleSqrt().domain( range_y ).range( [ obj['view']['height']['inner'], 0 ] ).nice()
642
+ } else {
643
+ y = d3.scaleLinear().domain( range_y ).range( [ obj['view']['height']['inner'], 0 ] ).nice()
644
+ }
645
+
646
+ result = {
647
+ 'x' : x,
648
+ 'y' : y
649
+ }
650
+ return result
651
+ }
652
+
653
+
654
+ let _d3_view = prepare_view_axes( _dataset, obj )
655
+ _d3_view['x_axis_range'] = d3.axisBottom( _d3_view['x'] )
656
+ .ticks( obj['data']['y']['ticks'] )
657
+ _d3_view['x_axis_range'] = d3.axisBottom( _d3_view['x'] )
658
+ .ticks( obj['data']['y']['ticks'] )
659
+ _d3_view['y_axis_range'] = d3.axisLeft( _d3_view['y'] )
660
+ .ticks( obj['data']['y']['ticks'] )
661
+ _d3_view['y_axis_grid'] = d3.axisLeft( _d3_view['y'] )
662
+ .tickSize( -obj['view']['width']['inner'] )
663
+ .tickFormat( '' )
664
+ .ticks( obj['data']['y']['ticks'] )
665
+ return _d3_view
666
+ }
667
+
668
+
669
+ const draw_create = ( _params, _dataset, _d3_view ) => {
670
+ const draw_grid = ( obj, _dataset, _d3_view ) => {
671
+ _d3_view['grid'] = document.createElement( 'div' )
672
+ _d3_view['grid'].setAttribute( 'id', obj['view']['dom_id'] )
673
+ document.body.appendChild( _d3_view['grid'] )
674
+
675
+ _d3_view['svg'] = d3.select( '#' + obj['view']['dom_id'] ).append( 'svg' )
676
+ .attr( 'width', obj['view']['width']['outer'] )
677
+ .attr( 'height', obj['view']['height']['outer'] )
678
+ .style( 'background-color', obj['style']['color']['canvas_background'] )
679
+ .append( 'g' )
680
+ .attr( 'transform', 'translate(' + obj['view']['margin']['left'] + ',' + obj['view']['margin']['top'] + ')' )
681
+
682
+ _d3_view['svg'].append( 'g' )
683
+ .attr( 'class', 'x axis-grid' )
684
+ .attr( 'transform', 'translate(0,' + obj['view']['height']['inner'] + ')' )
685
+
686
+ _d3_view['svg'].append( 'g')
687
+ .call( _d3_view['y_axis_grid'] )
688
+ .selectAll( 'g.tick' )
689
+ .select( 'line' )
690
+ .attr( 'class', 'quadrantBorder' )
691
+ .style( 'stroke-width', obj['style']['stroke']['gridline'] )
692
+ .style( 'color', obj['style']['color']['gridline'] )
693
+
694
+ _d3_view['svg'].append( 'g' )
695
+ .attr( 'class', 'x axis' )
696
+ .attr( 'transform', 'translate(0,' + obj['view']['height']['inner'] + ')' )
697
+ .call( _d3_view['x_axis_range'] )
698
+ .selectAll( 'text' )
699
+ .data( _dataset['points'] )
700
+ .attr( 'font-family', obj['style']['font']['family'] )
701
+ .style( 'fill', ( d ) => { return d['color_text'] } )
702
+ .attr( 'font-size', obj['style']['font']['size']['text'] )
703
+ .style( 'font-weight', ( d ) => { return d['font_format'] } )
704
+ .style( 'text-anchor', 'end' )
705
+ .attr( 'dx', '-.8em' )
706
+ .attr( 'dy', '-.55em' )
707
+ .attr( 'transform', 'translate(8,0) rotate(' + obj['style']['other']['range_x_text_rotation'] + ')' )
708
+
709
+ _d3_view['svg'].append( 'g' )
710
+ .attr( 'class', 'y axis' )
711
+ .call( _d3_view['y_axis_range'] )
712
+ .selectAll( 'text' )
713
+ .attr( 'font-family', obj['style']['font']['family'] )
714
+ .style( 'fill', obj['style']['color']['font'] )
715
+ .attr( 'font-size', obj['style']['font']['size']['text'] )
716
+
717
+ document.querySelectorAll( 'g.y.axis g.tick line' ).forEach( ( node ) => { node.remove() } )
718
+ document.querySelectorAll( 'g.x.axis g.tick line' ).forEach( ( node ) => { node.remove() } )
719
+ document.querySelectorAll( '.domain' ).forEach( ( node ) => { node.remove() } )
720
+ }
721
+
722
+
723
+ const draw_data = ( obj, _dataset, _d3_view ) => {
724
+ switch( _dataset['type'] ) {
725
+ case 'single':
726
+ switch( _dataset['view'] ) {
727
+ case 'bar' :
728
+ _d3_view['svg'].selectAll( 'bar' )
729
+ .data( _dataset['points'] )
730
+ .enter().append( 'rect' )
731
+ .attr( 'class', 'bar' )
732
+ .style( 'fill', ( d ) => { return d['color_chart'] } )
733
+ .attr( 'x', ( d ) => { return _d3_view['x']( d['name_trimmed'] ) } )
734
+ .attr( 'transform', 'translate(3,0)' )
735
+ .attr( 'width', _d3_view['x'].bandwidth() - 6 )
736
+ .attr( 'y', ( d ) => { return _d3_view['y']( d['value'] ) } )
737
+ .attr( 'height', ( d ) => { return obj['view']['height']['inner'] - _d3_view['y']( d['value'] ) } )
738
+ break;
739
+ case 'circle':
740
+ _d3_view['svg'].selectAll('circle')
741
+ .data( _dataset['points'] )
742
+ .enter().append( 'circle' )
743
+ .style( 'stroke-width', 0 )
744
+ .style( 'fill', ( d ) => { return d['color_chart'] } )
745
+ .attr( 'transform', 'translate(' + ( _d3_view['x'].bandwidth() / 2 ) +',0)' )
746
+ .attr( 'r', obj['style']['other']['circle_chart_radius'] )
747
+ .attr( 'cx', ( d ) => { return _d3_view['x']( d['name_trimmed'] ) } )
748
+ .attr( 'cy', ( d ) => { return _d3_view['y']( d['value'] ) } )
749
+ break;
750
+ }
751
+ break;
752
+ case 'stacked':
753
+ let color = d3.scaleOrdinal()
754
+ .domain( obj['data']['legend']['names']['keys'] )
755
+ .range( obj['style']['color']['legends'] )
756
+
757
+ let stacked_data = d3.stack().keys( obj['data']['legend']['names']['keys'] )( _dataset['points'] )
758
+
759
+ _d3_view['svg'].append( 'g' )
760
+ .selectAll( 'g' )
761
+ .data( stacked_data )
762
+ .enter().append( 'g' )
763
+ .attr( 'fill', ( d ) => { return color( d.key ) } )
764
+ .selectAll( 'rect' )
765
+ .data( ( d ) => { return d } )
766
+ .enter().append( 'rect' )
767
+ .attr( 'x', ( d ) => { return _d3_view['x']( d.data['name_trimmed'] ) } )
768
+ .attr( 'y', ( d ) => { return _d3_view['y']( d[ 1 ] ) } )
769
+ .attr( 'height', ( d ) => { return _d3_view['y']( d[ 0 ] ) - _d3_view['y']( d[ 1 ] ) } )
770
+ .attr( 'transform', 'translate(3,0)' )
771
+ .attr( 'width', _d3_view['x'].bandwidth() - 6 )
772
+ break;
773
+ }
774
+ }
775
+
776
+
777
+ const draw_average = ( obj, _dataset, _d3_view ) => {
778
+ if( obj['show']['average'] ) {
779
+ _d3_view['svg'].append( 'path' )
780
+ .datum( _dataset['points'] )
781
+ .attr( 'stroke', obj['style']['color']['average'] )
782
+ .attr( 'stroke-width', obj['style']['stroke']['average'] )
783
+ .attr( 'transform', 'translate(' + ( _d3_view['x'].bandwidth() / 2 ) + ',0)' )
784
+ .attr( 'd', d3.line()
785
+ .x( ( d ) => { return _d3_view['x']( d['name_trimmed'] ) } )
786
+ .y( ( d ) => { return _d3_view['y']( d['average']) } )
787
+ )
788
+
789
+ _d3_view['svg'].append( 'text' )
790
+ .attr( 'font-family', obj['style']['font']['family'] )
791
+ .style( 'fill', obj['style']['color']['font'] )
792
+ .attr( 'font-size', obj['style']['font']['size']['text'] )
793
+ .attr( 'transform', 'translate(' + ( obj['view']['width']['inner'] + obj['style']['legend']['padding']['before'] ) + ',' + _d3_view['y']( _dataset['average'] ) + ')' )
794
+ .attr( 'dy', '.35em' )
795
+ .attr( 'text-anchor', 'start' )
796
+ .text( _dataset['average'] )
797
+ }
798
+ }
799
+
800
+
801
+ const draw_title = ( obj, _d3_view ) => {
802
+ if( obj['show']['title'] ) {
803
+ _d3_view['svg'].append('text')
804
+ .attr( 'x', 0 )
805
+ .attr( 'y', obj['view']['translate']['title'] )
806
+ .style( 'fill', obj['style']['color']['font'] )
807
+ .text( obj['view']['title'] )
808
+ .attr( 'font-family', obj['style']['font']['family'] )
809
+ .attr( 'font-size', obj['style']['font']['size']['title'] )
810
+ }
811
+ }
812
+
813
+
814
+ const draw_legend = ( obj, _d3_view ) => {
815
+ if( obj['show']['legend'] ) {
816
+ let legend = _d3_view['svg'].selectAll( '.legend' )
817
+ .data( obj['data']['legend']['names']['titleized'] )
818
+ .enter()
819
+ .append( 'g' )
820
+ .attr( 'transform', ( d, i ) => { return 'translate(' + obj['view']['translate']['title_and_legend'][ i ] + ',' + '0)' } )
821
+ .attr( 'class','legend' )
822
+
823
+ legend.append( 'rect' )
824
+ .attr( 'width',obj['style']['legend']['rect_size']['full'] )
825
+ .attr( 'height',obj['style']['legend']['rect_size']['full'] )
826
+ .attr( 'fill', ( d, i ) => { return obj['style']['color']['legends'][ i ] } )
827
+ .attr( 'transform','translate(' + obj['view']['translate']['top_global'] + ', -20 )' )
828
+
829
+ legend.append( 'text')
830
+ .attr( 'font-size', obj['style']['font']['size']['text'] )
831
+ .attr( 'font-family', obj['style']['font']['family'] )
832
+ .style( 'fill', obj['style']['color']['font'] )
833
+ .attr( 'x',obj['style']['legend']['rect_size']['full'] )
834
+ .attr( 'y', obj['view']['translate']['legend_text'] )
835
+ .text( ( d ) => { return d } )
836
+ .attr( 'transform','translate(' + ( obj['view']['translate']['top_global'] + obj['style']['legend']['rect_size']['half'] ) + ', -20 )' )
837
+ }
838
+ }
839
+
840
+
841
+ draw_grid( _params, _dataset, _d3_view )
842
+ draw_data( _params, _dataset, _d3_view )
843
+ draw_average( _params, _dataset, _d3_view )
844
+ draw_title( _params, _d3_view )
845
+ draw_legend( _params, _d3_view )
846
+ return true
847
+ }
848
+
849
+
850
+ let params = params_create( default_values, x_key, y_keys, optional )
851
+ let data = data_create( file, params )
852
+ let d3_view = view_create( params, data )
853
+ draw_create( params, data, d3_view )
854
+ }