guidepost 0.2.14__tar.gz → 0.2.16__tar.gz

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,2 @@
1
+ include README.md
2
+ include LICENSE
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: guidepost
3
- Version: 0.2.14
3
+ Version: 0.2.16
4
4
  Summary: Guidepost. An overview visualization for understanding supercomputer queue data.
5
5
  Home-page: https://github.com/cscully-allison/guidepost
6
6
  Author: Connor Scully-Allison
@@ -22,6 +22,7 @@ Dynamic: classifier
22
22
  Dynamic: description
23
23
  Dynamic: description-content-type
24
24
  Dynamic: home-page
25
+ Dynamic: license-file
25
26
  Dynamic: requires-dist
26
27
  Dynamic: requires-python
27
28
  Dynamic: summary
@@ -212,7 +213,7 @@ Guidepost is licensed under the MIT License. See the `LICENSE` file for details.
212
213
 
213
214
  ## Acknowledgments
214
215
 
215
- Guidepost was developed under the auspices and with funding provided by the National Renewable Energy Laboratory (NREL).
216
+ Guidepost was developed under the auspices and with funding provided by the National Renewable Energy Laboratory (NREL), the National Science Foundation under NSF IIS-1844573 and IIS-2324465, and the Department of Energy under DE-SC0022044 and DE-SC0024635.
216
217
 
217
218
  ---
218
219
 
@@ -184,7 +184,7 @@ Guidepost is licensed under the MIT License. See the `LICENSE` file for details.
184
184
 
185
185
  ## Acknowledgments
186
186
 
187
- Guidepost was developed under the auspices and with funding provided by the National Renewable Energy Laboratory (NREL).
187
+ Guidepost was developed under the auspices and with funding provided by the National Renewable Energy Laboratory (NREL), the National Science Foundation under NSF IIS-1844573 and IIS-2324465, and the Department of Energy under DE-SC0022044 and DE-SC0024635.
188
188
 
189
189
  ---
190
190
 
@@ -1,11 +1,14 @@
1
1
  import * as d3 from "https://esm.sh/d3@7";
2
+ // import * as d3 from "d3";
3
+
4
+
2
5
  //layout vars
3
6
  const FACET_LAYOUT = {
4
7
  outer_margin: 30
5
8
  }
6
9
 
7
10
  const OVERVIEW_LAYOUT = {
8
- width: 1100,
11
+ width: 1000,
9
12
  height: 300,
10
13
  outer_margin: 10,
11
14
  inner_padding: 30
@@ -54,6 +57,8 @@ const num_cols = 150;
54
57
 
55
58
  const MIN_BAR_WIDTH = 45;
56
59
 
60
+ const SHARED_X_SCALE = false
61
+
57
62
  // COLORS
58
63
  const BLUE = 'rgba(32, 61, 192, 0.7)';
59
64
  const RICH_BLUE = 'rgb(32, 61, 192)';
@@ -231,7 +236,7 @@ class JSModel{
231
236
  * @param {string} col - The column to get summary statistics for.
232
237
  * @returns {Object} - The summary statistics for the column.
233
238
  */
234
- get_summary_stats(data, col){
239
+ get_summary_stats(data, col, index){
235
240
  let sum_stats = {};
236
241
 
237
242
  if(data.length > 0){
@@ -266,6 +271,7 @@ class JSModel{
266
271
  sum_stats.var = sum_stats.variance;
267
272
  sum_stats.average = sum_stats.avg;
268
273
  sum_stats.mean = sum_stats.avg;
274
+ sum_stats.count = data.length;
269
275
  }
270
276
  else{
271
277
  sum_stats.sum = 0;
@@ -278,8 +284,12 @@ class JSModel{
278
284
  sum_stats.std = 0;
279
285
  sum_stats.median = 0;
280
286
  sum_stats.med = 0;
287
+ sum_stats.count = 0;
281
288
  }
282
289
 
290
+
291
+ sum_stats.index = index;
292
+
283
293
 
284
294
  return sum_stats;
285
295
  }
@@ -366,6 +376,29 @@ class JSModel{
366
376
  return Math.log10(max) - Math.log10(min) > order;
367
377
  }
368
378
 
379
+
380
+ //box bins for a column
381
+ binValues(values, thresholds, accessor) {
382
+ const bins = [];
383
+ // Create an empty bin for each interval between consecutive thresholds
384
+ for (let i = 0; i < thresholds.length - 1; i++) {
385
+ bins.push([]);
386
+ }
387
+ // Place each value in the appropriate bin
388
+ values.forEach(d => {
389
+ const val = accessor(d);
390
+ for (let i = 0; i < thresholds.length - 1; i++) {
391
+ // For the last bin, include values equal to the upper bound
392
+ if (val >= thresholds[i] && (i === thresholds.length - 2 || val < thresholds[i + 1])) {
393
+ bins[i].push(d);
394
+ break;
395
+ }
396
+ }
397
+ });
398
+ return bins;
399
+ }
400
+
401
+
369
402
  /**
370
403
  * Calculates metrics for the rectangles of the summary view for a specified facet. Bins come into this function already oragnized
371
404
  * into columns delinated by the x_axis_thresholds. It's a user specified datetime variable.
@@ -377,7 +410,11 @@ class JSModel{
377
410
  let current_bins = this.faceted_bins[fac].column;
378
411
  let sum_stats = this.faceted_sum_stats[fac];
379
412
 
413
+ // console.log("CALC BOX METRICS: ", fac, current_bins, x_axis_thresholds, y_axis_thresholds);
414
+
380
415
  // Iterate over the columns that divide the data along the x axis
416
+
417
+ let col_indx = 0;
381
418
  for(let bin in current_bins){
382
419
  let filtered_bin;
383
420
 
@@ -393,48 +430,31 @@ class JSModel{
393
430
  }
394
431
  }
395
432
 
396
- let temp_box_stats = this.get_summary_stats(filtered_bin, this.vars.y);
397
-
433
+ // Get summary statistics for the entire column of data before it is split into rows
434
+ let temp_box_stats = this.get_summary_stats(filtered_bin, this.vars.y, col_indx);
398
435
  temp_box_stats.threshold = x_axis_thresholds[bin];
399
436
 
400
437
  temp_box_stats.bins = [];
401
-
402
- //box bins for this column
403
- let row_bins = d3.bin()
404
- .value(d => d[this.vars.y])
405
- .domain([sum_stats.y.min, sum_stats.y.max]).thresholds(y_axis_thresholds)(filtered_bin);
406
-
407
-
408
- //clamps down last bin(s) if more are produced than desired
409
- // idk why but d3 produces too many bins sometimes
410
- if(row_bins.length > y_axis_thresholds.length-1){
411
- let diff = (y_axis_thresholds.length-1)-row_bins.length;
412
- let head = row_bins.slice(0, diff);
413
-
414
- for(let i = row_bins.length-1; i > y_axis_thresholds.length-2; i--){
415
- head[head.length-1] = head[head.length-1].concat(row_bins[i]);
416
- }
417
- row_bins = head;
418
- }
438
+
439
+ const customBins = this.binValues(filtered_bin, y_axis_thresholds, d => d[this.vars.y]);
440
+
441
+ // Process each bin's summary statistics and update color scale range
442
+ temp_box_stats.bins = customBins.map((bin, index) => {
443
+ const stats = this.get_summary_stats(bin, this.vars.color);
444
+ stats.values = bin;
445
+ stats.std_ratio = stats.std / this.faceted_sum_stats[fac].color.std;
446
+ stats.threshold = y_axis_thresholds[index];
447
+ this.color_scale_range[0] = Math.min(this.color_scale_range[0], stats[this.vars.color_agg] ? stats[this.vars.color_agg] : this.color_scale_range[0]);
448
+ this.color_scale_range[1] = Math.max(this.color_scale_range[1], stats[this.vars.color_agg]);
449
+ return stats;
450
+ });
419
451
 
420
- //load individual boxes of values with summary statistics describing them
421
- for(let index in row_bins){
422
- let row = row_bins[index];
423
- let sum_stats = this.get_summary_stats(row, this.vars.color);
424
- sum_stats.values = row;
425
- sum_stats['std_ratio'] = sum_stats.std/this.faceted_sum_stats[fac].color.std;
426
- sum_stats.threshold = y_axis_thresholds[index];
427
- temp_box_stats.bins.push(sum_stats);
428
- this.color_scale_range[0] = Math.min(this.color_scale_range[0], sum_stats[this.vars.color_agg]);
429
- this.color_scale_range[1] = Math.max(this.color_scale_range[1], sum_stats[this.vars.color_agg]);
430
- }
431
452
 
432
453
  temp_box_stats.column_values = filtered_bin;
433
454
  this.faceted_bins[fac].column[bin] = temp_box_stats;
455
+ col_indx += 1;
434
456
  }
435
457
 
436
- // console.log("THE BINS END OF CALC BOX METRICS: ", this.faceted_bins[fac].column);
437
-
438
458
  }
439
459
 
440
460
  /**
@@ -443,7 +463,21 @@ class JSModel{
443
463
  * @returns {Array} - The sanitized and initialized data.
444
464
  */
445
465
  sanitize_and_intialize_data(data){
446
- this.global_sum_stats = {x:{},y:{},color:{}};
466
+ this.global_sum_stats = {
467
+ x:{
468
+ max: Number.MIN_SAFE_INTEGER,
469
+ min: Number.MAX_SAFE_INTEGER
470
+ },
471
+ y:{
472
+ max: Number.MIN_SAFE_INTEGER,
473
+ min: Number.MAX_SAFE_INTEGER
474
+ },
475
+ color:{
476
+ max: Number.MIN_SAFE_INTEGER,
477
+ min: Number.MAX_SAFE_INTEGER
478
+ },
479
+ num_cols: 0
480
+ };
447
481
  for(let fac of this.facets){
448
482
  //store data about what types of scales x and y are
449
483
  this.scale_types[fac] = {
@@ -466,14 +500,6 @@ class JSModel{
466
500
  }
467
501
 
468
502
 
469
- // this.global_sum_stats.x.max = Math.max(this.faceted_sum_stats[fac].x.max, this.global_sum_stats.x.max);
470
- // this.global_sum_stats.y.max = Math.max(this.faceted_sum_stats[fac].y.max, this.global_sum_stats.y.max);
471
- // this.global_sum_stats.color.max = Math.max(this.faceted_sum_stats[fac].color.max, this.global_sum_stats.color.max);
472
-
473
- // this.global_sum_stats.x.min = Math.min(this.faceted_sum_stats[fac].x.min, this.global_sum_stats.x.min);
474
- // this.global_sum_stats.y.min = Math.min(this.faceted_sum_stats[fac].y.min, this.global_sum_stats.y.min);
475
- // this.global_sum_stats.color.min = Math.min(this.faceted_sum_stats[fac].color.min, this.global_sum_stats.color.max);
476
-
477
503
 
478
504
  data[fac] = this.sanitize_data_for_log(data[fac], this.vars.y);
479
505
 
@@ -484,9 +510,21 @@ class JSModel{
484
510
 
485
511
  let sum_stats = this.faceted_sum_stats[fac];
486
512
 
513
+ this.global_sum_stats.x.max = Math.max(this.faceted_sum_stats[fac].x.max, this.global_sum_stats.x.max);
514
+ this.global_sum_stats.y.max = Math.max(this.faceted_sum_stats[fac].y.max, this.global_sum_stats.y.max);
515
+ this.global_sum_stats.color.max = Math.max(this.faceted_sum_stats[fac].color.max, this.global_sum_stats.color.max);
516
+
517
+ this.global_sum_stats.x.min = Math.min(this.faceted_sum_stats[fac].x.min, this.global_sum_stats.x.min);
518
+ this.global_sum_stats.y.min = Math.min(this.faceted_sum_stats[fac].y.min, this.global_sum_stats.y.min);
519
+ this.global_sum_stats.color.min = Math.min(this.faceted_sum_stats[fac].color.min, this.global_sum_stats.color.max);
520
+
487
521
 
488
522
  this.faceted_bins[fac] = {}
489
523
 
524
+
525
+
526
+ // console.log("SUM STATS: ", fac, sum_stats, "blahaj");
527
+
490
528
  //conditional x axis thresholds based on time or numbers
491
529
  // important for calculating the scales which layout the columns
492
530
  // of the "heatmap"
@@ -503,7 +541,7 @@ class JSModel{
503
541
  // just do linerats if not
504
542
  if(this.is_more_than_n_orders_of_magnitude(sum_stats.x.min, sum_stats.x.max, 3)){
505
543
  this.scale_types[fac].x.log = true;
506
- this.x_axis_thresholds[fac] = this.logScale(this.log_values_floor, sum_stats.x.max, num_cols);
544
+ this.x_axis_thresholds[fac] = this.logScale(this.log_values_floor, sum_stats.x.max+1, num_cols-1);
507
545
  this.faceted_bins[fac].column = d3.bin()
508
546
  .value(d => d[this.vars.x])
509
547
  .domain([this.log_values_floor, sum_stats.x.max])
@@ -512,7 +550,7 @@ class JSModel{
512
550
  }
513
551
  else{
514
552
  this.scale_types[fac].x.linear = true;
515
- this.x_axis_thresholds[fac] = this.linearScale(sum_stats.x.min, sum_stats.x.max, num_rows);
553
+ this.x_axis_thresholds[fac] = this.linearScale(sum_stats.x.min, sum_stats.x.max+1, num_cols-1);
516
554
  this.faceted_bins[fac].column = d3.bin()
517
555
  .value(d => d[this.vars.x])
518
556
  .domain([sum_stats.x.min, sum_stats.x.max])
@@ -538,8 +576,9 @@ class JSModel{
538
576
  sum_stats.col_counts.max = Math.max(sum_stats.col_counts.max, bin.length);
539
577
  sum_stats.col_counts.min = Math.min(sum_stats.col_counts.min, bin.length);
540
578
  }
579
+
580
+ this.global_sum_stats.num_cols = Math.max(this.faceted_bins[fac].column.length, this.global_sum_stats.num_cols);
541
581
 
542
- // temporary as Y AXIS IS FIXED LOG
543
582
  this.calculate_box_metrics(fac, this.x_axis_thresholds[fac], this.y_axis_thresholds[fac]);
544
583
  this.calc_row_major_counts(fac);
545
584
 
@@ -554,6 +593,7 @@ class JSModel{
554
593
  this.categorical_bins[fac] = Object.keys(cat_counts).map((key) => { return {"key": key, "val":cat_counts[key]} }).sort((a, b) => b['val'] - a['val']);
555
594
  }
556
595
 
596
+
557
597
  return data;
558
598
  }
559
599
 
@@ -582,8 +622,6 @@ class JSModel{
582
622
  */
583
623
  filter_data_by_category(filter, facet, source, targets){
584
624
 
585
- console.log("DATA FILTERS: ", filter)
586
-
587
625
  this.faceted_states[facet].filter = filter;
588
626
 
589
627
  // is anything pinned
@@ -635,9 +673,6 @@ class JSModel{
635
673
 
636
674
  }
637
675
 
638
- console.log(this.brushed_ranges[facet].x_range, this.brushed_ranges[facet].y_range);
639
-
640
-
641
676
 
642
677
  if(this.brushed_ranges[facet].x_range.length != 0){
643
678
  for(let bin of this.faceted_bins[facet].column){
@@ -683,8 +718,6 @@ class JSModel{
683
718
  }
684
719
  }
685
720
 
686
- console.log("TEST", test);
687
-
688
721
  this.anywidget_model.set("selected_records", JSON.stringify(return_ids));
689
722
  this.anywidget_model.save_changes();
690
723
 
@@ -815,7 +848,7 @@ class SmartScale {
815
848
  if(this.model.is_more_than_n_orders_of_magnitude(this.domain[0], this.domain[1], 3)){
816
849
  return d3.scaleLog().domain([this.model.log_values_floor, this.domain[1]]).range(this.range);
817
850
  } else {
818
- return d3.scaleLinear().domain([this.domain[0], this.domain[1]]).range(this.range);
851
+ return d3.scaleLinear().domain(this.domain).range(this.range);
819
852
  }
820
853
  } else {
821
854
  throw new Error("Unsupported domain type");
@@ -894,16 +927,23 @@ class Heatmap{
894
927
  // .domain(this.model.faceted_bins[this.facet].column.keys())
895
928
  // .range([OVERVIEW_LAYOUT.inner_padding, OVERVIEW_LAYOUT.width - OVERVIEW_LAYOUT.inner_padding]);
896
929
 
897
- this.scale_x = new SmartScale([sum_stats.x.min, sum_stats.x.max],
898
- [OVERVIEW_LAYOUT.inner_padding, OVERVIEW_LAYOUT.width-OVERVIEW_LAYOUT.inner_padding],
899
- this.model);
900
- // d3.scaleUtc()
901
- // .domain([new Date(sum_stats.x.min), this.model.addDays(new Date(sum_stats.x.max),1)])
902
- // .range([OVERVIEW_LAYOUT.inner_padding, OVERVIEW_LAYOUT.width-OVERVIEW_LAYOUT.inner_padding]);
930
+
931
+ if(SHARED_X_SCALE){
932
+ this.scale_x = new SmartScale([this.model.global_sum_stats.x.min, this.model.global_sum_stats.x.max],
933
+ [OVERVIEW_LAYOUT.inner_padding, OVERVIEW_LAYOUT.width-OVERVIEW_LAYOUT.inner_padding],
934
+ this.model);
935
+ }
936
+ else{
937
+ this.scale_x = new SmartScale([sum_stats.x.min, sum_stats.x.max],
938
+ [OVERVIEW_LAYOUT.inner_padding, OVERVIEW_LAYOUT.width-OVERVIEW_LAYOUT.inner_padding],
939
+ this.model);
940
+
941
+ }
942
+
903
943
 
904
944
  //Determine if y scale is log or linear based on input data
905
- console.log(this.model.scale_types[this.facet]);
906
945
  if(this.model.scale_types[this.facet].y.log){
946
+
907
947
  this.scale_y = d3.scaleLog()
908
948
  .domain([this.model.log_values_floor, sum_stats.y.max])
909
949
  .range([OVERVIEW_LAYOUT.inner_padding, OVERVIEW_LAYOUT.height - OVERVIEW_LAYOUT.inner_padding]);
@@ -956,9 +996,14 @@ class Heatmap{
956
996
  .attr('height', this.height);
957
997
 
958
998
 
999
+ let axis_left = d3.axisLeft().scale(this.scale_y_inverse);
1000
+ if(this.model.scale_types[this.facet].y.linear){
1001
+ axis_left.tickFormat(d3.format(".2s"));
1002
+ }
1003
+
959
1004
  view.append('g')
960
1005
  .attr('class', 'left-axis')
961
- .call(d3.axisLeft().scale(this.scale_y_inverse))
1006
+ .call(axis_left)
962
1007
  .attr('transform', `translate(${OVERVIEW_LAYOUT.inner_padding},${0})`);
963
1008
 
964
1009
  view.append('g')
@@ -1043,19 +1088,24 @@ class Heatmap{
1043
1088
  * Raises and zooms on a column slightly
1044
1089
  */
1045
1090
  focus_col(update_element){
1046
- let self = this;
1047
-
1048
- let base_width = Math.min(MIN_BAR_WIDTH, (draw_width / self.model.faceted_bins[self.facet].column.length))
1049
-
1050
- self.scale_y_blocks.range([OVERVIEW_LAYOUT.inner_padding-(zoom_factor_v/2), OVERVIEW_LAYOUT.height - OVERVIEW_LAYOUT.inner_padding + (zoom_factor_v/2)]);
1091
+ let self = this;
1092
+ let base_width;
1093
+ if(SHARED_X_SCALE){
1094
+ base_width = Math.min(MIN_BAR_WIDTH, (draw_width / self.model.global_sum_stats.num_cols))
1095
+ }
1096
+ else{
1097
+ base_width = Math.min(MIN_BAR_WIDTH, (draw_width / self.model.faceted_bins[self.facet].column.length))
1098
+ }
1099
+
1100
+ self.scale_y_blocks.range([OVERVIEW_LAYOUT.inner_padding-(zoom_factor_v/2), OVERVIEW_LAYOUT.height - OVERVIEW_LAYOUT.inner_padding + (zoom_factor_v/2)]);
1051
1101
 
1052
- update_element.raise();
1053
-
1054
- update_element.selectAll('.row')
1055
- .attr('width', ()=>{return base_width + zoom_factor_h})
1056
- .attr('height', ()=>{return ( (OVERVIEW_LAYOUT.height + zoom_factor_v) - 2*OVERVIEW_LAYOUT.inner_padding) / self.model.faceted_bins[self.facet].column[0].bins.length})
1057
- .attr('y', (d, i)=>{return self.scale_y_blocks(i) - OVERVIEW_LAYOUT.inner_padding});
1102
+ update_element.raise();
1058
1103
 
1104
+ update_element.selectAll('.row')
1105
+ .attr('width', ()=>{return base_width + zoom_factor_h})
1106
+ .attr('height', ()=>{return ( (OVERVIEW_LAYOUT.height + zoom_factor_v) - 2*OVERVIEW_LAYOUT.inner_padding) / self.model.faceted_bins[self.facet].column[0].bins.length})
1107
+ .attr('y', (d, i)=>{return self.scale_y_blocks(i) - OVERVIEW_LAYOUT.inner_padding});
1108
+
1059
1109
  update_element.selectAll('.col-bg')
1060
1110
  .attr('width', ()=>{return base_width + zoom_factor_h})
1061
1111
  .attr('height', ()=>{return ( (OVERVIEW_LAYOUT.height + zoom_factor_v) - 2*OVERVIEW_LAYOUT.inner_padding)})
@@ -1075,9 +1125,14 @@ class Heatmap{
1075
1125
  */
1076
1126
  unfocus_col(update_element){
1077
1127
  let self = this;
1078
-
1079
-
1080
- let base_width = Math.min(MIN_BAR_WIDTH, (draw_width / self.model.faceted_bins[self.facet].column.length))
1128
+ let base_width;
1129
+ if(SHARED_X_SCALE){
1130
+ base_width = Math.min(MIN_BAR_WIDTH, (draw_width / self.model.global_sum_stats.num_cols))
1131
+ }
1132
+ else{
1133
+ base_width = Math.min(MIN_BAR_WIDTH, (draw_width / self.model.faceted_bins[self.facet].column.length))
1134
+ }
1135
+
1081
1136
  self.scale_y_blocks.range([OVERVIEW_LAYOUT.inner_padding, OVERVIEW_LAYOUT.height - OVERVIEW_LAYOUT.inner_padding]);
1082
1137
 
1083
1138
 
@@ -1130,9 +1185,16 @@ class Heatmap{
1130
1185
  render(){
1131
1186
  const self = this;
1132
1187
 
1133
- console.log(this.model.faceted_bins[this.facet].column);
1134
1188
 
1135
- let base_width = Math.min(MIN_BAR_WIDTH, (draw_width / self.model.faceted_bins[self.facet].column.length))
1189
+ let base_width = 0;
1190
+ if(SHARED_X_SCALE){
1191
+ base_width = Math.min(MIN_BAR_WIDTH, (draw_width / self.model.global_sum_stats.num_cols))
1192
+ }
1193
+ else{
1194
+ base_width = Math.min(MIN_BAR_WIDTH, (draw_width / self.model.faceted_bins[self.facet].column.length))
1195
+ }
1196
+
1197
+
1136
1198
 
1137
1199
  if(self.model.row_major_counts[self.facet].length < 2){
1138
1200
  this.view
@@ -1142,6 +1204,7 @@ class Heatmap{
1142
1204
  .attr('transform', `translate(${draw_width/2},${draw_height/2})`)
1143
1205
  }
1144
1206
  else{
1207
+
1145
1208
  this.view
1146
1209
  .selectAll('.column')
1147
1210
  .data(this.model.faceted_bins[this.facet].column)
@@ -1214,6 +1277,9 @@ class Heatmap{
1214
1277
  )
1215
1278
  col.on('mouseenter', function (e, d){
1216
1279
  delete self.cached_bins['hover'];
1280
+
1281
+ console.log("HOVERING OVER: ", d);
1282
+
1217
1283
  self.focus_col(d3.select(e.target));
1218
1284
  if(!Object.keys(self.cached_bins).includes(String(d.threshold))){
1219
1285
  let dt_text_selection = d3.select(e.target).select('.text-field');
@@ -1364,10 +1430,21 @@ class Histogram{
1364
1430
  let y_offset = Y_VARIABLE_OFFSET + OVERVIEW_LAYOUT.height + HISTOGRAM_LAYOUT.outer_margin;
1365
1431
 
1366
1432
 
1433
+
1367
1434
  //create the histograms
1368
1435
  let h_hist = this.parent.append('g')
1369
1436
  .attr('class', 'faceted-h-hist')
1370
1437
  .attr('transform', `translate(${x_offset},${y_offset})`);
1438
+
1439
+ h_hist.append('rect')
1440
+ .attr('width', this.width - 2*HISTOGRAM_LAYOUT.inner_padding)
1441
+ .attr('height', this.height - HISTOGRAM_LAYOUT.inner_padding)
1442
+ .attr('fill', 'rgba(240,240,240)')
1443
+ .attr('transform', `translate(${HISTOGRAM_LAYOUT.inner_padding},${0})`);
1444
+
1445
+
1446
+ h_hist.append("g")
1447
+ .attr('class', 'bars');
1371
1448
 
1372
1449
  h_hist.append('g')
1373
1450
  .attr('class', 'left-axis')
@@ -1389,7 +1466,10 @@ class Histogram{
1389
1466
  .attr('text-anchor', 'middle')
1390
1467
  .attr('transform', `translate(${this.width/2},${this.height})`);
1391
1468
 
1469
+
1470
+
1392
1471
  this.view = h_hist;
1472
+
1393
1473
 
1394
1474
  this.brush = d3.brushX()
1395
1475
  .extent([[OVERVIEW_LAYOUT.inner_padding, 0], [OVERVIEW_LAYOUT.width - OVERVIEW_LAYOUT.inner_padding, this.height-HISTOGRAM_LAYOUT.inner_padding]])
@@ -1400,9 +1480,8 @@ class Histogram{
1400
1480
  select = selection.map(self.scale_x.scale.invert, self.scale_x.scale).map(d3.utcDay.round);
1401
1481
  }
1402
1482
  if(self.model.scale_types[self.facet]['x']['log'] || self.model.scale_types[self.facet]['x']['linear']){
1403
- select = selection.map(self.scale_x.scale.invert, self.scale_x.scale).map((d)=>{return Math.floor(d+1)});
1483
+ select = selection.map(self.scale_x.scale.invert, self.scale_x.scale).map((d)=>{return d});
1404
1484
  }
1405
- console.log(select);
1406
1485
  }else{
1407
1486
  select = [];
1408
1487
  }
@@ -1410,46 +1489,62 @@ class Histogram{
1410
1489
  });
1411
1490
 
1412
1491
  h_hist.append("g")
1492
+ .attr('class', 'h-brush')
1413
1493
  .call(this.brush);
1414
1494
  }
1415
1495
 
1416
1496
 
1417
1497
  else if(this.orientation == 'right'){
1418
1498
 
1419
- let x_offset = X_VARIABLE_OFFSET + OVERVIEW_LAYOUT.width;
1499
+ let x_offset = X_VARIABLE_OFFSET + OVERVIEW_LAYOUT.width - 5;
1420
1500
  let y_offset = Y_VARIABLE_OFFSET + VERT_HISTOGRAM_LAYOUT.outer_margin;
1421
1501
 
1422
1502
  let v_hist = this.parent.append('g')
1423
1503
  .attr('class', 'faceted-v-hist')
1424
1504
  .attr('transform', `translate(${x_offset},${y_offset})`);
1425
1505
 
1506
+ v_hist.append('rect')
1507
+ .attr('width', this.width)
1508
+ .attr('height', this.height - 2*HISTOGRAM_LAYOUT.inner_padding)
1509
+ .attr('fill', 'rgba(240,240,240)')
1510
+ .attr('transform', `translate(${0},${HISTOGRAM_LAYOUT.inner_padding})`);
1511
+ ;
1512
+
1426
1513
  v_hist.append('g')
1427
1514
  .attr('class', 'bot-axis')
1428
1515
  .call(d3.axisBottom().scale(this.scale_x).ticks(5))
1429
1516
  .attr('transform', `translate(${VERT_HISTOGRAM_LAYOUT.inner_padding*4},${VERT_HISTOGRAM_LAYOUT.height - OVERVIEW_LAYOUT.inner_padding})`);
1430
1517
 
1431
- // v_hist.append('g')
1432
- // .attr('class', 'left-axis')
1433
- // .call(d3.axisLeft().scale(this.scale_y_inverse))
1434
- // .attr('transform', `translate(${VERT_HISTOGRAM_LAYOUT.inner_padding},${0})`);
1518
+ v_hist.append('g')
1519
+ .attr('class', 'left-axis')
1520
+ .call(d3.axisRight().scale(this.axis_scale_y_inverse))
1521
+ .attr('transform', `translate(${self.width-VERT_HISTOGRAM_LAYOUT.inner_padding},${0})`);
1435
1522
 
1436
1523
  this.brush = d3.brushY()
1437
1524
  .extent([[0, HISTOGRAM_LAYOUT.inner_padding], [this.width, this.height - OVERVIEW_LAYOUT.inner_padding]])
1438
1525
  .on("end", function({selection}){
1439
1526
  let select;
1440
1527
  if(selection){
1441
- select = selection.map(self.scale_y.invert, self.scale_y).map((d)=>{return Math.floor(d+1)})
1528
+ select = selection.map(self.scale_y.invert, self.scale_y).map((d)=>{return d+0.1})
1442
1529
  }else{
1443
1530
  select = [];
1444
1531
  }
1445
1532
  self.model.update_subselected_data(self.facet, [`${self.facet}_heatmap`, `${self.facet}_legend`], select, "y");
1446
1533
  });
1447
1534
 
1535
+
1448
1536
  v_hist.append("g")
1537
+ .attr('class', 'bars');
1538
+
1539
+ v_hist.append("g")
1540
+ .attr('class', 'v-brush')
1449
1541
  .call(this.brush);
1450
1542
 
1451
1543
  this.view = v_hist;
1452
1544
  }
1545
+
1546
+
1547
+
1453
1548
  }
1454
1549
 
1455
1550
  /**
@@ -1469,20 +1564,56 @@ class Histogram{
1469
1564
 
1470
1565
  //references OVERVIEW LAYOUT SIZES
1471
1566
  //BE CAREFUL
1472
- this.scale_x = new SmartScale([sum_stats.x.min, sum_stats.x.max],
1567
+ if(SHARED_X_SCALE){
1568
+ this.scale_x = new SmartScale([this.model.global_sum_stats.x.min, this.model.global_sum_stats.x.max],
1569
+ [OVERVIEW_LAYOUT.inner_padding, OVERVIEW_LAYOUT.width-OVERVIEW_LAYOUT.inner_padding],
1570
+ this.model);
1571
+ }
1572
+ else{
1573
+ this.scale_x = new SmartScale([sum_stats.x.min, sum_stats.x.max],
1473
1574
  [OVERVIEW_LAYOUT.inner_padding, OVERVIEW_LAYOUT.width-OVERVIEW_LAYOUT.inner_padding],
1474
1575
  this.model);
1576
+ }
1577
+
1475
1578
  }
1476
1579
 
1477
1580
  else if(this.orientation == 'right'){
1478
1581
 
1479
- this.scale_y = d3.scaleLinear()
1582
+ if(this.model.is_more_than_n_orders_of_magnitude(0, Math.max(...this.model.row_major_counts[this.facet]), 3)){
1583
+ let local_log_floor = 0.3
1584
+ this.scale_x = d3.scaleLog()
1585
+ .domain([local_log_floor, Math.max(...this.model.row_major_counts[this.facet])])
1586
+ .range([0, VERT_HISTOGRAM_LAYOUT.width - VERT_HISTOGRAM_LAYOUT.inner_padding]);
1587
+ }else{
1588
+ this.scale_x = d3.scaleLinear()
1589
+ .domain([0, Math.max(...this.model.row_major_counts[this.facet])])
1590
+ .range([0, VERT_HISTOGRAM_LAYOUT.width - VERT_HISTOGRAM_LAYOUT.inner_padding]);
1591
+ }
1592
+
1593
+
1594
+ if(this.model.scale_types[this.facet].y.log){
1595
+ this.axis_scale_y = d3.scaleLog()
1596
+ .domain([this.model.log_values_floor, sum_stats.y.max])
1597
+ .range([OVERVIEW_LAYOUT.inner_padding, OVERVIEW_LAYOUT.height - OVERVIEW_LAYOUT.inner_padding]);
1598
+
1599
+ this.axis_scale_y_inverse = d3.scaleLog()
1600
+ .domain([sum_stats.y.max, this.model.log_values_floor])
1601
+ .range([OVERVIEW_LAYOUT.inner_padding, OVERVIEW_LAYOUT.height - OVERVIEW_LAYOUT.inner_padding]);
1602
+ }
1603
+ else if(this.model.scale_types[this.facet].y.linear){
1604
+ this.axis_scale_y = d3.scaleLinear()
1605
+ .domain([sum_stats.y.min, sum_stats.y.max])
1606
+ .range([OVERVIEW_LAYOUT.inner_padding, OVERVIEW_LAYOUT.height - OVERVIEW_LAYOUT.inner_padding]);
1607
+
1608
+ this.axis_scale_y_inverse = d3.scaleLinear()
1609
+ .domain([sum_stats.y.max, sum_stats.y.min])
1610
+ .range([OVERVIEW_LAYOUT.inner_padding, OVERVIEW_LAYOUT.height - OVERVIEW_LAYOUT.inner_padding]);
1611
+ }
1612
+
1613
+ this.scale_y = d3.scaleLinear()
1480
1614
  .domain([num_rows-2, -1])
1481
1615
  .range([OVERVIEW_LAYOUT.inner_padding, OVERVIEW_LAYOUT.height - OVERVIEW_LAYOUT.inner_padding]);
1482
1616
 
1483
- this.scale_x = d3.scaleLinear()
1484
- .domain([0, Math.max(...this.model.row_major_counts[this.facet])])
1485
- .range([0, VERT_HISTOGRAM_LAYOUT.width - VERT_HISTOGRAM_LAYOUT.inner_padding]);
1486
1617
  }
1487
1618
  }
1488
1619
 
@@ -1491,12 +1622,23 @@ class Histogram{
1491
1622
  */
1492
1623
  render(){
1493
1624
  const self = this;
1494
- let bar_width = Math.min(MIN_BAR_WIDTH, (draw_width / self.model.faceted_bins[self.facet].column.length))
1625
+ let bar_width = 0;
1626
+ let axis_height = 1;
1495
1627
 
1628
+ if(SHARED_X_SCALE){
1629
+ bar_width = Math.min(MIN_BAR_WIDTH, (draw_width / self.model.global_sum_stats.num_cols))
1630
+ }
1631
+ else{
1632
+ bar_width = Math.min(MIN_BAR_WIDTH, (draw_width / self.model.faceted_bins[self.facet].column.length))
1633
+ }
1634
+
1635
+ let bar_layer = this.view.select('.bars');
1636
+
1637
+
1496
1638
  if(self.model.row_major_counts[self.facet].length > 2){
1497
1639
  if(this.orientation == 'bottom'){
1498
- this.view.selectAll('.column')
1499
- .data(self.model.faceted_bins[self.facet].column, function(d){return this.id} )
1640
+ bar_layer.selectAll('.column')
1641
+ .data(self.model.faceted_bins[self.facet].column, function(d){console.log("INDEX", d.index); return d.index} )
1500
1642
  .join(
1501
1643
  function(enter){
1502
1644
  let col = enter.append('g')
@@ -1513,19 +1655,20 @@ class Histogram{
1513
1655
  .attr('height', (d)=>{return self.scale_y(d.column_values.length)})
1514
1656
  .attr('width', bar_width)
1515
1657
  .attr('fill', TAN)
1516
- .attr(`transform`, (d)=>{return `translate(${0}, ${(HISTOGRAM_LAYOUT.height- self.scale_y(d.column_values.length))-2*HISTOGRAM_LAYOUT.inner_padding})`});
1658
+ .attr(`transform`, (d)=>{return `translate(${0}, ${(HISTOGRAM_LAYOUT.height- self.scale_y(d.column_values.length))-2*HISTOGRAM_LAYOUT.inner_padding - axis_height})`});
1517
1659
  },
1518
1660
  function(update){
1519
- update.selectAll('rect')
1661
+ update.select('.bar')
1520
1662
  .transition()
1663
+ .duration(500)
1521
1664
  .attr('height', (d,i)=>{return self.scale_y(self.model.faceted_bins[self.facet].column[i].column_values.length)})
1665
+ .attr(`transform`, (d, i)=>{return `translate(${0}, ${(HISTOGRAM_LAYOUT.height- self.scale_y(self.model.faceted_bins[self.facet].column[i].column_values.length))-2*HISTOGRAM_LAYOUT.inner_padding - axis_height})`});
1522
1666
  }
1523
1667
  );
1524
1668
  }
1525
1669
 
1526
1670
  if(this.orientation == "right"){
1527
- this.view
1528
- .selectAll('.row')
1671
+ bar_layer.selectAll('.row')
1529
1672
  .data(self.model.row_major_counts[self.facet])
1530
1673
  .join(
1531
1674
  function(enter){
@@ -1535,7 +1678,9 @@ class Histogram{
1535
1678
 
1536
1679
  row.append('rect')
1537
1680
  .attr('class', 'bar')
1538
- .attr('width', (d)=>{return self.scale_x(d)})
1681
+ .attr('width', (d)=>{
1682
+ return self.scale_x(d) ? self.scale_x(d) : 0;
1683
+ })
1539
1684
  .attr('height', (d)=>{return draw_height / self.model.faceted_bins[self.facet].column[0].bins.length})
1540
1685
  .attr('fill', TAN);
1541
1686
 
@@ -1544,7 +1689,9 @@ class Histogram{
1544
1689
  function(update){
1545
1690
  update.select('.bar')
1546
1691
  .transition()
1547
- .attr('width', (d)=>{return self.scale_x(d)});
1692
+ .attr('width', (d)=>{
1693
+ return self.scale_x(d) ? self.scale_x(d) : 0;
1694
+ });
1548
1695
  },
1549
1696
  function(exit){
1550
1697
  exit.remove();
@@ -1552,6 +1699,7 @@ class Histogram{
1552
1699
  )
1553
1700
  }
1554
1701
  }
1702
+
1555
1703
  }
1556
1704
  }
1557
1705
 
@@ -1586,8 +1734,8 @@ class CategoricalBarChart{
1586
1734
 
1587
1735
  //create the histograms
1588
1736
 
1589
- let x_offset = X_VARIABLE_OFFSET + HISTOGRAM_LAYOUT.outer_margin + OVERVIEW_LAYOUT.width;
1590
- let y_offset = Y_VARIABLE_OFFSET + OVERVIEW_LAYOUT.height + HISTOGRAM_LAYOUT.outer_margin;
1737
+ let x_offset = X_VARIABLE_OFFSET + OVERVIEW_LAYOUT.width;
1738
+ let y_offset = Y_VARIABLE_OFFSET + HISTOGRAM_LAYOUT.outer_margin + OVERVIEW_LAYOUT.height ;
1591
1739
 
1592
1740
  let h_hist = this.parent.append('g')
1593
1741
  .attr('class', 'faceted-h-hist')
@@ -1648,15 +1796,31 @@ class CategoricalBarChart{
1648
1796
  setup_scales(){
1649
1797
  this.n = Math.min(this.model.categorical_bins[this.facet].length, this.n);
1650
1798
  let top_n_cats = this.model.categorical_bins[this.facet].slice(0,this.n);
1799
+
1800
+ this.max_bar_width = 30;
1801
+ this.drawable_width = (this.width-2*CAT_HISTOGRAM_LAYOUT.inner_padding);
1802
+ this.calc_bar_width = Math.min(this.max_bar_width, this.drawable_width/this.n);
1651
1803
 
1652
1804
  if(this.orientation == 'bottom'){
1653
- this.scale_y = d3.scaleLinear()
1654
- .domain([0, top_n_cats[0].val])
1655
- .range([0, this.height - CAT_HISTOGRAM_LAYOUT.inner_padding]);
1656
-
1657
- this.scale_y_inverse = d3.scaleLinear()
1658
- .domain([top_n_cats[0].val, 0])
1659
- .range([0, this.height - CAT_HISTOGRAM_LAYOUT.inner_padding]);
1805
+ if(this.model.is_more_than_n_orders_of_magnitude(0, top_n_cats[0].val, 3)){
1806
+ let local_log_floor = 0.3
1807
+ this.scale_y = d3.scaleLog()
1808
+ .domain([local_log_floor, top_n_cats[0].val])
1809
+ .range([0, this.height - CAT_HISTOGRAM_LAYOUT.inner_padding]);
1810
+
1811
+ this.scale_y_inverse = d3.scaleLog()
1812
+ .domain([top_n_cats[0].val, local_log_floor])
1813
+ .range([0, this.height - CAT_HISTOGRAM_LAYOUT.inner_padding]);
1814
+ }
1815
+ else{
1816
+ this.scale_y = d3.scaleLinear()
1817
+ .domain([0, top_n_cats[0].val])
1818
+ .range([0, this.height - CAT_HISTOGRAM_LAYOUT.inner_padding]);
1819
+
1820
+ this.scale_y_inverse = d3.scaleLinear()
1821
+ .domain([top_n_cats[0].val, 0])
1822
+ .range([0, this.height - CAT_HISTOGRAM_LAYOUT.inner_padding]);
1823
+ }
1660
1824
 
1661
1825
  //references OVERVIEW LAYOUT SIZES
1662
1826
  //BE CAREFUL
@@ -1664,7 +1828,8 @@ class CategoricalBarChart{
1664
1828
  .domain(top_n_cats.map((obj)=>{
1665
1829
  return obj.key;
1666
1830
  }))
1667
- .range([CAT_HISTOGRAM_LAYOUT.inner_padding, this.width - CAT_HISTOGRAM_LAYOUT.inner_padding]);
1831
+ .range([CAT_HISTOGRAM_LAYOUT.inner_padding, this.width - CAT_HISTOGRAM_LAYOUT.inner_padding])
1832
+ .padding(0.1);
1668
1833
  }
1669
1834
 
1670
1835
  else if(this.orientation == 'right'){
@@ -1684,11 +1849,10 @@ class CategoricalBarChart{
1684
1849
  */
1685
1850
  render(){
1686
1851
 
1852
+ const self = this;
1687
1853
  let top_n_cats = this.model.categorical_bins[this.facet].slice(0,this.n);
1688
1854
  let update_targets = [`${this.facet}_heatmap`, `${this.facet}_right_histogram`, `${this.facet}_bottom_histogram`, `${this.facet}_legend`];
1689
1855
 
1690
- const self = this;
1691
-
1692
1856
  if(self.model.row_major_counts[self.facet].length > 2){
1693
1857
 
1694
1858
  if(this.orientation == 'bottom'){
@@ -1705,17 +1869,18 @@ class CategoricalBarChart{
1705
1869
  let col = enter.append('g')
1706
1870
  .attr('class', 'column')
1707
1871
  .attr('transform', (d, i)=>{
1708
- if(self.scale_x(d.key)){
1709
- return `translate(${self.scale_x(d.key)}, ${CAT_HISTOGRAM_LAYOUT.inner_padding})`
1710
- }
1711
- return `translate(${0}, ${CAT_HISTOGRAM_LAYOUT.inner_padding})`
1872
+ const tickPos = self.scale_x(d.key);
1873
+ const bandWidth = self.scale_x.bandwidth();
1874
+ // Center bar if calc_bar_width < bandWidth
1875
+ const offset = (bandWidth - self.calc_bar_width) / 2;
1876
+ return `translate(${tickPos + offset}, ${CAT_HISTOGRAM_LAYOUT.inner_padding})`;
1712
1877
  });
1713
1878
 
1714
1879
  col.append('rect')
1715
1880
  .attr('class', 'bar')
1716
1881
  .attr('height', (d)=>{return self.scale_y(d.val)})
1717
1882
  // .attr('width', (d)=>{return ((HISTOGRAM_LAYOUT.width - 2*HISTOGRAM_LAYOUT.inner_padding) / faceted_bins[d.facet].x.length)})
1718
- .attr('width', (self.width-2*CAT_HISTOGRAM_LAYOUT.inner_padding)/self.n)
1883
+ .attr('width', self.calc_bar_width)
1719
1884
  .attr('fill', TAN)
1720
1885
  .attr(`transform`, (d)=>{return `translate(${0}, ${(CAT_HISTOGRAM_LAYOUT.height- self.scale_y(d.val))-2*CAT_HISTOGRAM_LAYOUT.inner_padding})`})
1721
1886
  .on('mouseover', function (e,d){
@@ -1786,6 +1951,7 @@ class Legend {
1786
1951
  if(color_scale.domain().length > 2){
1787
1952
  this.ticks_scale = d3.scaleDiverging().domain(color_scale.domain().reverse()).range([0, this.bar_height/2, this.bar_height]);
1788
1953
  } else{
1954
+ console.log("DOMAIN at LEGEND CREATE", color_scale.domain());
1789
1955
  if(color_scale.domain()[1] > 2){
1790
1956
  this.ticks_scale = d3.scaleSymlog().domain([color_scale.domain()[0]+1, color_scale.domain()[1]].reverse()).range([0, this.bar_height]);
1791
1957
  }
@@ -1910,7 +2076,6 @@ class Validator{
1910
2076
  */
1911
2077
  isValidDate(dateString) {
1912
2078
  const date_time = new Date(dateString);
1913
- console.log("AAAAA", date_time.getTime());
1914
2079
  return !isNaN(date_time.getTime());
1915
2080
  }
1916
2081
 
@@ -2002,6 +2167,15 @@ class Validator{
2002
2167
  return missing;
2003
2168
  }
2004
2169
 
2170
+
2171
+ // Function to coerce an entire column’s values to strings
2172
+ coerceColumnToString(columnData) {
2173
+ return Object.keys(columnData).reduce((result, key) => {
2174
+ result[key] = String(columnData[key]);
2175
+ return result;
2176
+ }, {});
2177
+ }
2178
+
2005
2179
  /**
2006
2180
  * Ensures that all values in this.var_specs are logically appropriate
2007
2181
  * @param {Object} this.var_specs - The variable specifications.
@@ -2023,11 +2197,11 @@ class Validator{
2023
2197
  if (typeof test_val !== 'number'){
2024
2198
  if(typeof test_val == 'string'){
2025
2199
  if(!this.isValidDate(test_val)){
2026
- incorrect.push({ key: key, value: this.var_specs[key], message: 'The x-axis aaaaa only supports floats, integers and dates. Please specify a different variable or verify that the datetime is properly formatted.' });
2200
+ incorrect.push({ key: key, value: this.var_specs[key], message: 'The x-axis only supports floats, integers and dates. Please specify a different variable or verify that the datetime is properly formatted.' });
2027
2201
  }
2028
2202
  }
2029
2203
  else {
2030
- incorrect.push({ key: key, value: this.var_specs[key], message: 'The x-axis bbbbbb only supports floats, integers and dates. Please specify a different variable or verify that the datetime is properly formatted.' });
2204
+ incorrect.push({ key: key, value: this.var_specs[key], message: 'The x-axis only supports floats, integers and dates. Please specify a different variable or verify that the datetime is properly formatted.' });
2031
2205
  }
2032
2206
  }
2033
2207
  }
@@ -2045,8 +2219,15 @@ class Validator{
2045
2219
  }
2046
2220
  else if (key === 'categorical'){
2047
2221
  let test_val = this.data[this.var_specs[key]][Object.keys(this.data[this.var_specs[key]])[0]];
2222
+ // For categorical variables, coerce data to strings if necessary.
2048
2223
  if(typeof test_val !== 'string'){
2049
- incorrect.push({ key: key, value: this.var_specs[key], message: 'The categorical view only supports categorical variables formatted as strings. Please specify a different column on your dataset or reformat an exisitng column.' });
2224
+ // Coerce the column data at this.data[this.var_specs[key]]
2225
+ this.data[this.var_specs[key]] = coerceColumnToString(this.data[this.var_specs[key]]);
2226
+ // Re-check the data type after coercion
2227
+ test_val = this.data[this.var_specs[key]][Object.keys(this.data[this.var_specs[key]])[0]];
2228
+ if(typeof test_val !== 'string'){
2229
+ incorrect.push({ key: key, value: this.var_specs[key], message: 'The categorical view only supports categorical variables formatted as strings. Please specify a different column on your dataset or reformat an existing column.' });
2230
+ }
2050
2231
  }
2051
2232
  }
2052
2233
  }
@@ -2144,7 +2325,6 @@ function render({model, el}){
2144
2325
 
2145
2326
  validator.data = data;
2146
2327
  is_valid = validator.validate();
2147
- console.log()
2148
2328
 
2149
2329
  if(is_valid){
2150
2330
  let jsmodel = new JSModel(data, var_specs, model);
@@ -2157,4 +2337,4 @@ function render({model, el}){
2157
2337
 
2158
2338
 
2159
2339
 
2160
- export default{ render }
2340
+ export default{ render };
@@ -5,6 +5,7 @@ import numpy as np
5
5
  import warnings
6
6
  import json
7
7
  import os
8
+ import sys
8
9
 
9
10
  class Guidepost(anywidget.AnyWidget):
10
11
  _esm = os.path.join(os.path.dirname(__file__), "guidepost.js")
@@ -24,6 +25,9 @@ class Guidepost(anywidget.AnyWidget):
24
25
  in_cpy.insert(0, 'gp_idx', range(0, len(in_cpy)))
25
26
  self.cached_records_df = in_cpy
26
27
 
28
+ if sys.version_info.major < 3 or sys.version_info.minor < 12:
29
+ raise EnvironmentError("Python 3.12 or greater is required to run this library.")
30
+
27
31
  _warn_skips = (os.path.dirname('.'),)
28
32
  original_cols = in_cpy.columns
29
33
  o_df = in_cpy.dropna(axis=1, how='all')
@@ -0,0 +1,2 @@
1
+ __version_info__ = ("0", "2", "16")
2
+ __version__ = ".".join(__version_info__)
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: guidepost
3
- Version: 0.2.14
3
+ Version: 0.2.16
4
4
  Summary: Guidepost. An overview visualization for understanding supercomputer queue data.
5
5
  Home-page: https://github.com/cscully-allison/guidepost
6
6
  Author: Connor Scully-Allison
@@ -22,6 +22,7 @@ Dynamic: classifier
22
22
  Dynamic: description
23
23
  Dynamic: description-content-type
24
24
  Dynamic: home-page
25
+ Dynamic: license-file
25
26
  Dynamic: requires-dist
26
27
  Dynamic: requires-python
27
28
  Dynamic: summary
@@ -212,7 +213,7 @@ Guidepost is licensed under the MIT License. See the `LICENSE` file for details.
212
213
 
213
214
  ## Acknowledgments
214
215
 
215
- Guidepost was developed under the auspices and with funding provided by the National Renewable Energy Laboratory (NREL).
216
+ Guidepost was developed under the auspices and with funding provided by the National Renewable Energy Laboratory (NREL), the National Science Foundation under NSF IIS-1844573 and IIS-2324465, and the Department of Energy under DE-SC0022044 and DE-SC0024635.
216
217
 
217
218
  ---
218
219
 
@@ -1,9 +1,8 @@
1
1
  LICENSE
2
+ MANIFEST.in
2
3
  README.md
3
4
  pyproject.toml
4
5
  setup.py
5
- figs/__init__.py
6
- figs/guidepost_tutorial_info.png
7
6
  guidepost/__init__.py
8
7
  guidepost/guidepost.js
9
8
  guidepost/guidepost.py
@@ -1,3 +1,2 @@
1
- figs
2
1
  guidepost
3
2
  tutorials
@@ -4,7 +4,7 @@ from os import path
4
4
 
5
5
  here = path.abspath(path.dirname(__file__))
6
6
 
7
- with open("README.md", "r", encoding="utf-8") as fh:
7
+ with open(path.join(here, "README.md"), "r", encoding="utf-8") as fh:
8
8
  long_description = fh.read()
9
9
 
10
10
  # Get the version in a safe way
@@ -1,2 +0,0 @@
1
- __version_info__ = ("0", "2", "14")
2
- __version__ = ".".join(__version_info__)
File without changes
File without changes
File without changes
File without changes