statosio 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ }