mapdata 2.13.0__tar.gz → 2.14.0__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mapdata
3
- Version: 2.13.0
3
+ Version: 2.14.0
4
4
  Summary: An interactive map and table explorer for geographic coordinates in a spreadsheet, CSV file, or database
5
5
  Home-page: https://osdn.net/project/mapdata/
6
6
  Author: Dreas Nielsen
@@ -24,8 +24,8 @@
24
24
  #
25
25
  # ==================================================================
26
26
 
27
- version = "2.13.0"
28
- vdate = "2023-12-30"
27
+ version = "2.14.0"
28
+ vdate = "2023-12-31"
29
29
 
30
30
  copyright = "2023"
31
31
 
@@ -459,20 +459,21 @@ class CsvFile(object):
459
459
  return self
460
460
 
461
461
 
462
- def sort_columns(columns):
463
- # Sorts a list of one, two, or three sublists, where each sublist is a column. Rows are sorted by the first column.
462
+ def sort_columns(columns, sortby=0):
463
+ # Sorts a list of one, two, or three sublists, where each sublist is a column.
464
+ # Rows are sorted by the 'sortby' column, which is zero-based.
464
465
  # The returned value is also column-wise, but sorted by rows.
465
466
  if len(columns) == 1:
466
467
  return sorted(columns)
467
468
  nrows = len(columns[0])
468
469
  if len(columns) == 2:
469
470
  rowdata = [[columns[0][i], columns[1][i]] for i in range(nrows)]
470
- rowdata.sort()
471
+ rowdata.sort(key = lambda c: c[sortby])
471
472
  return [[rowdata[i][0] for i in range(nrows)], [rowdata[i][1] for i in range(nrows)]]
472
473
  else:
473
474
  # len(columns) should be 3, though this is not checked
474
475
  rowdata = [[columns[0][i], columns[1][i], columns[2][i]] for i in range(nrows)]
475
- rowdata.sort()
476
+ rowdata.sort(key = lambda c: c[sortby])
476
477
  return [[rowdata[i][0] for i in range(nrows)], [rowdata[i][1] for i in range(nrows)], [rowdata[i][2] for i in range(nrows)]]
477
478
 
478
479
 
@@ -913,6 +914,7 @@ class MapUI(object):
913
914
  self.basemap_var = tk.StringVar(self.win, bm_name)
914
915
  self.map_option_menu = ttk.Combobox(ctrlframe, state="readonly", textvariable=self.basemap_var,
915
916
  values=self.available_tile_servers(), width=18)
917
+ self.map_option_menu["state"] = "normal"
916
918
  self.map_option_menu.bind('<<ComboboxSelected>>', self.change_basemap)
917
919
  self.map_option_menu.grid(row=0, column=1, padx=(5, 30), pady=(2, 5), sticky=tk.W)
918
920
  # Multi-select option
@@ -1416,8 +1418,9 @@ class MapUI(object):
1416
1418
  # Plotting support. Return all data for the specified columns as a list of column-oriented lists.
1417
1419
  res = []
1418
1420
  for c in column_list:
1419
- i = self.headers.index(c)
1420
- res.append([row[i] for row in self.rows])
1421
+ if c in self.headers:
1422
+ i = self.headers.index(c)
1423
+ res.append([row[i] for row in self.rows])
1421
1424
  return res
1422
1425
  def get_sel_data(self, column_list):
1423
1426
  # Plotting support. Return data from selected rows for the specified columns, as a list of lists.
@@ -1469,6 +1472,9 @@ class MapUI(object):
1469
1472
  clone.dlg.bind("<Alt-y>", clone.set_ylabel)
1470
1473
  if clone.type_var.get() == "Histogram":
1471
1474
  clone.dlg.bind("<Alt-b>", clone.set_bins)
1475
+ clone.alpha = plot_obj.alpha
1476
+ if clone.type_var.get() in ("Scatter plot", "Categorical stripchart", "Categorical KD plot"):
1477
+ clone.dlg.bind("<Alt-a>", clone.set_alpha)
1472
1478
  clone.dataset = copy.copy(plot_obj.dataset)
1473
1479
  clone.plot_data = copy.copy(plot_obj.plot_data)
1474
1480
  clone.data_labels = copy.copy(plot_obj.data_labels)
@@ -2795,10 +2801,16 @@ class PlotDialog(object):
2795
2801
  self.dlg.columnconfigure(0, weight=1)
2796
2802
  self.auto_update = True
2797
2803
  self.plot_title = None
2804
+ # For transparency on some plots
2805
+ self.alpha = 0.45
2798
2806
  # For histogram
2799
2807
  self.bins = 10
2800
2808
  # For display of groups at Jenks breaks on Q-Q plots
2801
2809
  self.qq_groups = False
2810
+ # For display of lines at X and Y Jenks breaks on scatter plots
2811
+ self.scatter_breaks = False
2812
+ self.scatter_x_breaks = None
2813
+ self.scatter_y_breaks = None
2802
2814
 
2803
2815
  def set_autoupdate():
2804
2816
  if self.autoupdate_var.get() == "1":
@@ -2930,7 +2942,7 @@ class PlotDialog(object):
2930
2942
  for j in range(variables):
2931
2943
  row.append(self.dataset[j][i])
2932
2944
  rowwise_data.append(row)
2933
- tframe, tdata = treeview_table(dlg.content_frame, rowwise_data, self.data_labels)
2945
+ tframe, tdata = treeview_table(dlg.content_frame, rowwise_data, self.data_labels[0:variables])
2934
2946
  tframe.grid(row=0, column=0, sticky=tk.NSEW)
2935
2947
  dlg.show()
2936
2948
 
@@ -2981,6 +2993,7 @@ class PlotDialog(object):
2981
2993
  # Also sets the groupby Combobox values if applicable.
2982
2994
  self.plotfig.clear()
2983
2995
  self.plot_title = None
2996
+ self.dlg.bind("<Alt-a>")
2984
2997
  self.dlg.bind("<Alt-b>")
2985
2998
  self.dlg.bind("<Alt-g>")
2986
2999
  self.plot_axes = self.plotfig.add_subplot(111)
@@ -2999,8 +3012,8 @@ class PlotDialog(object):
2999
3012
  # quant_columns includes date and timestamp columns
3000
3013
  quant_columns = [c[0] for c in self.column_specs if c[1] in ("int", "float", "date", "timestamp", "timestamptz")]
3001
3014
  quant_columns.sort()
3002
- numeric_columns = [c[0] for c in self.column_specs if c[1] in ("int", "float")]
3003
- numeric_columns.sort()
3015
+ self.numeric_columns = [c[0] for c in self.column_specs if c[1] in ("int", "float")]
3016
+ self.numeric_columns.sort()
3004
3017
  date_columns = [c[0] for c in self.column_specs if c[1] in ("date", "timestamp", "timestamptz")]
3005
3018
  date_columns.sort()
3006
3019
  self.x_var.set('')
@@ -3020,7 +3033,7 @@ class PlotDialog(object):
3020
3033
  self.ylog_ck["state"] = "disabled"
3021
3034
  elif plot_type in ("Histogram", "Empirical CDF", "Kernel density (KD) plot", "Normal Q-Q plot", "Breaks groups", "Breaks optimum"):
3022
3035
  self.x_sel["state"] = "normal"
3023
- self.x_sel["values"] = numeric_columns
3036
+ self.x_sel["values"] = self.numeric_columns
3024
3037
  self.y_sel["state"] = "disabled"
3025
3038
  self.ylog_ck["state"] = "disabled"
3026
3039
  if plot_type == "Histogram":
@@ -3033,7 +3046,8 @@ class PlotDialog(object):
3033
3046
  self.x_sel["state"] = "normal"
3034
3047
  self.x_sel["values"] = xcols
3035
3048
  self.xlog_ck["state"] = "disabled"
3036
- self.y_sel["values"] = numeric_columns
3049
+ self.y_sel["state"] = "normal"
3050
+ self.y_sel["values"] = self.numeric_columns
3037
3051
  elif plot_type == "Min-max plot":
3038
3052
  self.x_sel["state"] = "normal"
3039
3053
  self.x_sel["values"] = quant_columns
@@ -3047,7 +3061,7 @@ class PlotDialog(object):
3047
3061
  self.y_sel["values"] = quant_columns
3048
3062
  if plot_type in ("Scatter plot", "Line plot"):
3049
3063
  self.groupby_sel["state"] = "normal"
3050
- self.groupby_sel["values"] = [''] + categ_columns2
3064
+ self.groupby_sel["values"] = [''] + categ_columns2 + ['* Breaks in X', '* Breaks in Y']
3051
3065
 
3052
3066
  def q_redraw(self, get_data=True, *args):
3053
3067
  # Conditionally (re)draw the plot.
@@ -3117,8 +3131,9 @@ class PlotDialog(object):
3117
3131
  # Set data labels
3118
3132
  if self.y_var.get() != '':
3119
3133
  self.data_labels = [self.x_var.get(), self.y_var.get()]
3120
- if self.groupby_var.get() != '':
3121
- self.data_labels.append(self.groupby_var.get())
3134
+ grpvar = self.groupby_var.get()
3135
+ if grpvar not in ('', '* Breaks in X', '* Breaks in Y') :
3136
+ self.data_labels.append(grpvar)
3122
3137
  else:
3123
3138
  self.data_labels = [self.x_var.get()]
3124
3139
  # Log-transform data if specified.
@@ -3246,18 +3261,49 @@ class PlotDialog(object):
3246
3261
  self.plot_data_labels = [self.data_labels[0], self.data_labels[1] + " min", self.data_labels[1] + " max"]
3247
3262
  elif plot_type == "Line plot":
3248
3263
  # Sort by X
3249
- if self.groupby_var.get() == '':
3250
- ds = list(zip(self.dataset[0], self.dataset[1]))
3251
- else:
3252
- ds = list(zip(self.dataset[0], self.dataset[1], self.dataset[2]))
3253
- ds.sort()
3254
- ds2 = list(zip(*ds))
3264
+ #if self.groupby_var.get() == '':
3265
+ # ds = list(zip(self.dataset[0], self.dataset[1]))
3266
+ #else:
3267
+ # ds = list(zip(self.dataset[0], self.dataset[1], self.dataset[2]))
3268
+ #ds.sort()
3269
+ #ds2 = list(zip(*ds))
3270
+ ds2 = sort_columns(self.dataset)
3255
3271
  if self.groupby_var.get() == '':
3256
3272
  self.plot_data = [list(ds2[0]), list(ds2[1])]
3257
3273
  else:
3258
3274
  self.plot_data = [list(ds2[0]), list(ds2[1]), list(ds2[2])]
3259
3275
  self.plot_data_labels = self.data_labels
3260
- elif plot_type in ("Histogram", "Scatter plot", "Y range plot"):
3276
+ elif plot_type == "Scatter plot":
3277
+ if self.groupby_var.get() == "* Breaks in X":
3278
+ if self.x_var.get() in self.numeric_columns:
3279
+ ds = sort_columns(self.dataset)
3280
+ oj = optimum_jenks(ds[0], 8)
3281
+ jnb = jenkspy.JenksNaturalBreaks(oj)
3282
+ jnb.fit(ds[0])
3283
+ self.plot_data = [ds[0], ds[1], ["Group "+str(lbl+1) for lbl in jnb.labels_]]
3284
+ self.plot_data_labels = self.data_labels + ['Breaks in X']
3285
+ else:
3286
+ # Can't compute breaks for X
3287
+ self.groupby_var.set('')
3288
+ self.plot_data = self.dataset
3289
+ self.plot_data_labels = self.data_labels
3290
+ elif self.groupby_var.get() == "* Breaks in Y":
3291
+ if self.y_var.get() in self.numeric_columns:
3292
+ ds = sort_columns(self.dataset, sortby=1)
3293
+ oj = optimum_jenks(ds[1], 8)
3294
+ jnb = jenkspy.JenksNaturalBreaks(oj)
3295
+ jnb.fit(ds[1])
3296
+ self.plot_data = [ds[0], ds[1], ["Group "+str(lbl+1) for lbl in jnb.labels_]]
3297
+ self.plot_data_labels = self.data_labels + ['Breaks in Y']
3298
+ else:
3299
+ # Can't compute breaks for Y
3300
+ self.groupby_var.set('')
3301
+ self.plot_data = self.dataset
3302
+ self.plot_data_labels = self.data_labels
3303
+ else:
3304
+ self.plot_data = self.dataset
3305
+ self.plot_data_labels = self.data_labels
3306
+ elif plot_type in ("Histogram", "Y range plot"):
3261
3307
  # No special preparation
3262
3308
  self.plot_data = self.dataset
3263
3309
  self.plot_data_labels = self.data_labels
@@ -3275,8 +3321,29 @@ class PlotDialog(object):
3275
3321
  self.plot_axes.set_xlabel(self.x_var.get())
3276
3322
  self.plot_axes.set_ylabel("Counts")
3277
3323
  elif plot_type == "Scatter plot":
3324
+ self.dlg.bind("<Alt-a>", self.set_alpha)
3325
+ self.dlg.bind("<Alt-b>", self.set_scatter_breaks)
3326
+ if self.scatter_breaks:
3327
+ if self.x_var.get() in self.numeric_columns:
3328
+ x_vals = copy.copy(self.plot_data[0])
3329
+ x_vals.sort()
3330
+ oj = optimum_jenks(x_vals, 8)
3331
+ jnb = jenkspy.JenksNaturalBreaks(oj)
3332
+ jnb.fit(x_vals)
3333
+ self.scatter_x_breaks = jnb.inner_breaks_
3334
+ for b in self.scatter_x_breaks:
3335
+ self.plot_axes.axvline(b, color='0.8', alpha=0.5)
3336
+ if self.y_var.get() in self.numeric_columns:
3337
+ y_vals = copy.copy(self.plot_data[1])
3338
+ y_vals.sort()
3339
+ oj = optimum_jenks(y_vals, 8)
3340
+ jnb = jenkspy.JenksNaturalBreaks(oj)
3341
+ jnb.fit(y_vals)
3342
+ self.scatter_y_breaks = jnb.inner_breaks_
3343
+ for b in self.scatter_y_breaks:
3344
+ self.plot_axes.axhline(b, color='0.8', alpha=0.5)
3278
3345
  if self.groupby_var.get() == '':
3279
- self.plot_axes.scatter(self.plot_data[0], self.plot_data[1])
3346
+ self.plot_axes.scatter(self.plot_data[0], self.plot_data[1], alpha=self.alpha)
3280
3347
  else:
3281
3348
  groups = list(set(self.plot_data[2]))
3282
3349
  groups.sort()
@@ -3284,7 +3351,7 @@ class PlotDialog(object):
3284
3351
  for g in groups:
3285
3352
  pdx = [self.plot_data[0][i] for i in range(datarows) if self.plot_data[2][i] == g]
3286
3353
  pdy = [self.plot_data[1][i] for i in range(datarows) if self.plot_data[2][i] == g]
3287
- self.plot_axes.plot(pdx, pdy, marker='o', linestyle='', label=g)
3354
+ self.plot_axes.plot(pdx, pdy, marker='o', alpha=self.alpha, linestyle='', label=g)
3288
3355
  self.plot_axes.legend()
3289
3356
  self.plot_axes.set_xlabel(self.plot_data_labels[0])
3290
3357
  self.plot_axes.set_ylabel(self.plot_data_labels[1])
@@ -3362,14 +3429,16 @@ class PlotDialog(object):
3362
3429
  self.plot_axes.set_xlabel(self.x_var.get())
3363
3430
  self.plot_axes.set_ylabel(self.data_labels[1])
3364
3431
  elif plot_type == "Categorical KD plot":
3432
+ self.dlg.bind("<Alt-a>", self.set_alpha)
3365
3433
  sns.kdeplot({self.x_var.get(): self.dataset[0], self.y_var.get(): self.dataset[1]},
3366
- x=self.y_var.get(), hue=self.x_var.get(), multiple="layer", fill=True, alpha=0.35, ax=self.plot_axes,
3434
+ x=self.y_var.get(), hue=self.x_var.get(), multiple="layer", fill=True, alpha=self.alpha, ax=self.plot_axes,
3367
3435
  warn_singular=False)
3368
3436
  self.plot_axes.set_xlabel(self.y_var.get())
3369
3437
  self.plot_axes.set_ylabel("Density")
3370
3438
  elif plot_type == "Categorical stripchart":
3439
+ self.dlg.bind("<Alt-a>", self.set_alpha)
3371
3440
  sns.stripplot({self.x_var.get(): self.dataset[0], self.y_var.get(): self.dataset[1]},
3372
- x=self.x_var.get(), y=self.y_var.get(), alpha=0.4, ax=self.plot_axes)
3441
+ x=self.x_var.get(), y=self.y_var.get(), alpha=self.alpha, ax=self.plot_axes)
3373
3442
  self.plot_axes.set_xlabel(self.x_var.get())
3374
3443
  self.plot_axes.set_ylabel(self.data_labels[1])
3375
3444
  elif plot_type == "Violin plot":
@@ -3408,6 +3477,20 @@ class PlotDialog(object):
3408
3477
  self.bins = num_bins
3409
3478
  if self.type_var.get() == "Histogram":
3410
3479
  self.q_redraw()
3480
+ def set_alpha(self, *args):
3481
+ dlg = OneFloatDialog(self.dlg, "Transparency", "Enter the transparency (alpha) value", min_value=0.0, max_value=1.0, initial=self.alpha)
3482
+ new_alpha = dlg.show()
3483
+ if new_alpha is not None:
3484
+ self.alpha = new_alpha
3485
+ if self.type_var.get() in ("Scatter plot", "Categorical stripchart", "Categorical KD plot"):
3486
+ self.q_redraw()
3487
+ def set_scatter_breaks(self, *args):
3488
+ # Toggle display of vertical and horizontal lines at natural breaks values on a scatter plot.
3489
+ # plot_data[0] and plot_data[1] must be x and y, respectively.
3490
+ self.scatter_breaks = not self.scatter_breaks
3491
+ if self.type_var.get() == "Scatter plot":
3492
+ self.q_redraw()
3493
+
3411
3494
  def show_groups(self, *args):
3412
3495
  if self.type_var.get() == "Normal Q-Q plot":
3413
3496
  self.qq_groups = not self.qq_groups
@@ -3625,6 +3708,53 @@ class OneIntDialog(object):
3625
3708
  return self.entry_var.get()
3626
3709
 
3627
3710
 
3711
+ class OneFloatDialog(object):
3712
+ def __init__(self, parent, title, prompt, min_value, max_value, initial):
3713
+ self.dlg = tk.Toplevel(parent)
3714
+ self.dlg.title(title)
3715
+ prompt_frame = tk.Frame(self.dlg)
3716
+ prompt_frame.grid(row=0, column=0, sticky=tk.NSEW, pady=(3,3))
3717
+ prompt_frame.columnconfigure(0, weight=1)
3718
+ msg_lbl = ttk.Label(prompt_frame, text=prompt)
3719
+ msg_lbl.grid(row=0, column=0, sticky=tk.W, padx=(6,6), pady=(6,6))
3720
+ self.entry_var = tk.DoubleVar(self.dlg, initial)
3721
+ self.val_entry = ttk.Spinbox(prompt_frame, textvariable=self.entry_var, from_=min_value, to=max_value)
3722
+ self.val_entry.grid(row=1, column=0, sticky=tk.EW, padx=(6,6), pady=(3,3))
3723
+ btn_frame = tk.Frame(self.dlg, borderwidth=3, relief=tk.RIDGE)
3724
+ btn_frame.columnconfigure(0, weight=1)
3725
+ btn_frame.grid(row=2, column=0, sticky=tk.EW, pady=(3,3))
3726
+ btn_frame.columnconfigure(0, weight=1)
3727
+ self.val_entry.focus()
3728
+ # Buttons
3729
+ self.canceled = False
3730
+ self.ok_btn = ttk.Button(btn_frame, text="OK", command=self.do_select, underline=0)
3731
+ self.ok_btn.grid(row=0, column=0, sticky=tk.E, padx=(6,3))
3732
+ self.dlg.bind('<Alt-o>', self.do_select)
3733
+ cancel_btn = ttk.Button(btn_frame, text="Cancel", command=self.do_cancel, underline=0)
3734
+ cancel_btn.grid(row=0, column=1, sticky=tk.E, padx=(3,6))
3735
+ self.dlg.bind("<Escape>", self.do_cancel)
3736
+ self.dlg.bind("<Alt-c>", self.do_cancel)
3737
+ self.dlg.bind("<Return>", self.do_select)
3738
+ self.dlg.bind("<Alt-o>", self.do_select)
3739
+ def do_cancel(self, *args):
3740
+ self.canceled = True
3741
+ self.dlg.destroy()
3742
+ def do_select(self, *args):
3743
+ self.canceled = False
3744
+ self.dlg.destroy()
3745
+ def show(self):
3746
+ self.dlg.grab_set()
3747
+ center_window(self.dlg)
3748
+ raise_window(self.dlg)
3749
+ self.dlg.resizable(True, False)
3750
+ self.dlg.attributes('-topmost', 'true')
3751
+ self.dlg.wait_window(self.dlg)
3752
+ if self.canceled:
3753
+ return None
3754
+ else:
3755
+ return self.entry_var.get()
3756
+
3757
+
3628
3758
  class GetEditorDialog(object):
3629
3759
  def __init__(self, parent, current_editor):
3630
3760
  self.dlg = tk.Toplevel(parent)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mapdata
3
- Version: 2.13.0
3
+ Version: 2.14.0
4
4
  Summary: An interactive map and table explorer for geographic coordinates in a spreadsheet, CSV file, or database
5
5
  Home-page: https://osdn.net/project/mapdata/
6
6
  Author: Dreas Nielsen
@@ -5,7 +5,7 @@ with io.open('README.md', encoding='utf-8') as f:
5
5
  long_description = f.read()
6
6
 
7
7
  setuptools.setup(name='mapdata',
8
- version='2.13.0',
8
+ version='2.14.0',
9
9
  description="An interactive map and table explorer for geographic coordinates in a spreadsheet, CSV file, or database",
10
10
  author='Dreas Nielsen',
11
11
  author_email='dreas.nielsen@gmail.com',
File without changes
File without changes
File without changes
File without changes
File without changes