diff --git a/tagmaps/classes/cluster.py b/tagmaps/classes/cluster.py index a5b792b..233436d 100644 --- a/tagmaps/classes/cluster.py +++ b/tagmaps/classes/cluster.py @@ -22,6 +22,7 @@ from tagmaps.classes.shared_structure import (EMOJI, LOCATIONS, TAGS, AnalysisBounds, CleanedPost, ClusterType, PreparedStats) +from tagmaps.classes.plotting import TPLT from tagmaps.classes.utils import Utils with warnings.catch_warnings(): # filter sklearn\externals\joblib\parallel.py:268: @@ -715,7 +716,7 @@ def _get_sel_preview(self, item): points = self._get_np_points( item=item, silent=True) - fig = Utils._get_sel_preview(points, item, self.bounds) + fig = TPLT._get_sel_preview(points, item, self.bounds) return fig def _get_cluster_preview(self, item): @@ -726,7 +727,7 @@ def _get_cluster_preview(self, item): self._cluster_points( points=points, preview_mode=True) - fig = Utils._get_cluster_preview( + fig = TPLT._get_cluster_preview( points, self.sel_colors, item, self.bounds, self.mask_noisy, self.cluster_distance, self.number_of_clusters, self.autoselect_clusters) diff --git a/tagmaps/classes/interface.py b/tagmaps/classes/interface.py index 0480bdc..21f02dd 100644 --- a/tagmaps/classes/interface.py +++ b/tagmaps/classes/interface.py @@ -18,6 +18,7 @@ from typing import List, Set, Dict, Tuple, Optional, TextIO import traceback import shapely.geometry as geometry +from tagmaps.classes.plotting import TPLT from tagmaps.classes.utils import Utils from tagmaps.classes.shared_structure import ( CleanedPost, AnalysisBounds, @@ -55,7 +56,7 @@ def __init__(self, self.abort = False # self.floater_x = 0 # self.floater_y = 0 - self.img_ratio = Utils._get_img_ratio(self._clst.bounds) + self.img_ratio = TPLT._get_img_ratio(self._clst.bounds) self.current_display_item = None # Initialize TKinter Interface self.app = App() @@ -254,7 +255,7 @@ def _cluster_preview(self, sel_item: Tuple[str, int]): if self.fig1: # plt references the last figure accessed plt.figure(1).clf() - self.fig1 = Utils._get_cluster_preview( + self.fig1 = TPLT._get_cluster_preview( points, sel_colors, sel_item[0], self._clst.bounds, mask_noisy, self._clst.cluster_distance, self._clst.number_of_clusters, self._clst.autoselect_clusters) @@ -288,7 +289,7 @@ def _cluster_preview(self, sel_item: Tuple[str, int]): # label_clusters=True) # plt.title('Condensed Tree', fontsize=12,loc='center') self._set_plt_suptitle(sel_item[0]) - Utils.set_plt_tick_params(plt) + TPLT.set_plt_tick_params(plt) if self.fig3: plt.figure(3).clf() self._set_plt_suptitle(sel_item[0]) @@ -344,7 +345,7 @@ def _cluster_preview(self, sel_item: Tuple[str, int]): vals = self.fig3.get_yticks() self.fig3.set_yticklabels( [f'{Utils._get_meters_from_radians(x):3.1f}m' for x in vals]) - Utils.set_plt_tick_params(plt) + TPLT.set_plt_tick_params(plt) if self.create_min_spanning_tree: if self.fig4: plt.figure(4).clf() @@ -398,7 +399,7 @@ def _cluster_preview(self, sel_item: Tuple[str, int]): # cb.ax.set_yticklabels( # ['{:3.1f}m'.format(getMetersFromRadians(x)) for x in vals] # ) - Utils.set_plt_tick_params(plt) + TPLT.set_plt_tick_params(plt) self._update_scalebar() def _set_plt_suptitle(self, item: str): @@ -413,7 +414,7 @@ def _set_pltspec_suptitle(self, plt, item: str, cls_type=None): plt.rcParams['font.family'] = 'DejaVu Sans' else: plt.rcParams['font.family'] = 'sans-serif' - Utils._set_plt_suptitle_st(plt, title) + TPLT._set_plt_suptitle_st(plt, title) def _get_pltspec_suptitle(self, item: str, cls_type=None) -> str: """Gets formatted suptitle for plot @@ -452,7 +453,7 @@ def _intf_selection_preview(self, sel_item: Tuple[str, int]): def _intf_plot_points(self, item_name: str, points): self._set_plt_suptitle(item_name) - self.fig1 = Utils._get_fig_points( + self.fig1 = TPLT._get_fig_points( points, self.img_ratio, self._clst.bounds) def _report_callback_exception(self, exc, val, tb): diff --git a/tagmaps/classes/plotting.py b/tagmaps/classes/plotting.py index 29492bb..edde526 100644 --- a/tagmaps/classes/plotting.py +++ b/tagmaps/classes/plotting.py @@ -1,4 +1,124 @@ # -*- coding: utf-8 -*- """Module for matplotlib, seaborn, pyplot methods. -""" \ No newline at end of file +""" +from typing import List, Set, Dict, Tuple, Optional, TextIO, Iterable +import matplotlib.pyplot as plt +from descartes import PolygonPatch +from tagmaps.classes.shared_structure import CleanedPost, AnalysisBounds + + +class TPLT(): + """Tag Maps plotting Class + """ + PLOT_KWDS = {'alpha': 0.5, 's': 10, 'linewidths': 0} + + @staticmethod + def _get_xy_dists( + bounds: AnalysisBounds) -> Tuple[float, float]: + """Get X/Y Distances from Analysis Bounds""" + dist_y_lat = ( + bounds.lim_lat_max - bounds.lim_lat_min) + dist_x_lng = ( + bounds.lim_lng_max - bounds.lim_lng_min) + return dist_y_lat, dist_x_lng + + @staticmethod + def _get_img_ratio(bounds: AnalysisBounds + ) -> float: + """Gets [img] ratio form bounds.""" + dists = TPLT._get_xy_dists(bounds) + dist_y_lat = dists[0] + dist_x_lng = dists[1] + # distYLat = Utils.haversine(limXMin,limYMax,limXMin,limYMin) + # distXLng = Utils.haversine(limXMax,limYMin,limXMin,limYMin) + img_ratio = dist_x_lng/(dist_y_lat*2) + return img_ratio + + @staticmethod + def plt_setxy_lim(plt, bounds: AnalysisBounds): + """Set global plotting bounds basedon Analysis Bounds""" + plt.gca().set_xlim( + [bounds.lim_lng_min, bounds.lim_lng_max]) + plt.gca().set_ylim( + [bounds.lim_lat_min, bounds.lim_lat_max]) + + @staticmethod + def _get_fig_points(points, img_ratio, bounds): + plt.scatter(points.T[0], points.T[1], + color='red', **TPLT.PLOT_KWDS) + fig = plt.figure(num=1, figsize=( + 11, int(11*img_ratio)), dpi=80) + fig.canvas.set_window_title('Preview Map') + TPLT.plt_setxy_lim(plt, bounds) + plt.tick_params(labelsize=10) + return fig + + @staticmethod + def _get_sel_preview(points, item, bounds): + """Returns plt map for item selection preview""" + img_ratio = TPLT._get_img_ratio(bounds) + fig = TPLT._get_fig_points(points, img_ratio, bounds) + plt.suptitle(item, fontsize=18, fontweight='bold') + return fig + + @staticmethod + def _get_cluster_preview( + points, sel_colors, item_text, bounds, mask_noisy, + cluster_distance, number_of_clusters, auto_select_clusters=None): + if auto_select_clusters is None: + auto_select_clusters = False + # create main cluster points map + plt.scatter(points.T[0], points.T[1], + c=sel_colors, **TPLT.PLOT_KWDS) + img_ratio = TPLT._get_img_ratio(bounds) + fig1 = plt.figure(num=1, figsize=( + 11, int(11*img_ratio)), dpi=80) + fig1.canvas.set_window_title('Cluster Preview') + TPLT._set_plt_suptitle_st(plt, item_text) + dist_text = '' + if auto_select_clusters is False: + dist_text = '@ ' + str(cluster_distance) + 'm' + plt.title(f'Cluster Preview {dist_text}', + fontsize=12, loc='center') + # xmax = fig1.get_xlim()[1] + # ymax = fig1.get_ylim()[1] + noisy_txt = '{} / {}'.format(mask_noisy.sum(), len(mask_noisy)) + plt.text(bounds.lim_lng_max, + bounds.lim_lat_max, + f'{number_of_clusters} Clusters (Noise: {noisy_txt})', + fontsize=10, horizontalalignment='right', + verticalalignment='top', fontweight='bold') + # set plotting bounds + TPLT.plt_setxy_lim(plt, bounds) + TPLT.set_plt_tick_params(plt) + # define new figure so this one is not + # overwritten in interactive notebook mode + # plt.figure() + return fig1 + + @staticmethod + def set_plt_tick_params(plt): + """Sets common plt tick params""" + plt.tick_params(labelsize=10) + + @staticmethod + def _set_plt_suptitle_st(plt, title: str): + """Set title of plt""" + plt.suptitle(title, + fontsize=18, fontweight='bold') + + @staticmethod + def plot_polygon(polygon): + """Plot a polygon in matplotlib pyplot interface""" + fig = plt.figure(figsize=(10, 10)) + ax = fig.add_subplot(111) + margin = .3 + x_min, y_min, x_max, y_max = polygon.bounds + ax.set_xlim([x_min-margin, x_max+margin]) + ax.set_ylim([y_min-margin, y_max+margin]) + patch = PolygonPatch(polygon, fc='#999999', + ec='#000000', fill=True, + zorder=-1) + ax.add_patch(patch) + return fig diff --git a/tagmaps/classes/utils.py b/tagmaps/classes/utils.py index 0f80745..24b6583 100644 --- a/tagmaps/classes/utils.py +++ b/tagmaps/classes/utils.py @@ -19,14 +19,12 @@ import regex from importlib import reload import shapely.geometry as geometry -import matplotlib.pyplot as plt from fiona.crs import from_epsg from pathlib import Path from shapely.ops import transform, cascaded_union, polygonize from datetime import timedelta from typing import List, Set, Dict, Tuple, Optional, TextIO, Iterable from math import radians, cos, sin, asin, sqrt -from descartes import PolygonPatch from tagmaps.classes.shared_structure import CleanedPost, AnalysisBounds @@ -35,7 +33,6 @@ class Utils(): Primarily @classmethods and @staticmethods """ - PLOT_KWDS = {'alpha': 0.5, 's': 10, 'linewidths': 0} @staticmethod def _get_shapely_bounds( @@ -475,21 +472,6 @@ def str2bool(str_text): raise argparse.ArgumentTypeError( 'Boolean value expected.') - @staticmethod - def plot_polygon(polygon): - """Plot a polygon in matplotlib pyplot interface""" - fig = plt.figure(figsize=(10, 10)) - ax = fig.add_subplot(111) - margin = .3 - x_min, y_min, x_max, y_max = polygon.bounds - ax.set_xlim([x_min-margin, x_max+margin]) - ax.set_ylim([y_min-margin, y_max+margin]) - patch = PolygonPatch(polygon, fc='#999999', - ec='#000000', fill=True, - zorder=-1) - ax.add_patch(patch) - return fig - @staticmethod def get_rectangle_bounds(points): limYMin = np.min(points.T[1]) @@ -586,95 +568,3 @@ def _get_index_of_tup( return pos # Matches behavior of list.index raise ValueError("list.index(x): x not in list") - - @staticmethod - def _get_xy_dists( - bounds: AnalysisBounds) -> Tuple[float, float]: - """Get X/Y Distances from Analysis Bounds""" - dist_y_lat = ( - bounds.lim_lat_max - bounds.lim_lat_min) - dist_x_lng = ( - bounds.lim_lng_max - bounds.lim_lng_min) - return dist_y_lat, dist_x_lng - - @staticmethod - def _get_img_ratio(bounds: AnalysisBounds - ) -> float: - """Gets [img] ratio form bounds.""" - dists = Utils._get_xy_dists(bounds) - dist_y_lat = dists[0] - dist_x_lng = dists[1] - # distYLat = Utils.haversine(limXMin,limYMax,limXMin,limYMin) - # distXLng = Utils.haversine(limXMax,limYMin,limXMin,limYMin) - img_ratio = dist_x_lng/(dist_y_lat*2) - return img_ratio - - @staticmethod - def plt_setxy_lim(plt, bounds: AnalysisBounds): - """Set global plotting bounds basedon Analysis Bounds""" - plt.gca().set_xlim( - [bounds.lim_lng_min, bounds.lim_lng_max]) - plt.gca().set_ylim( - [bounds.lim_lat_min, bounds.lim_lat_max]) - - @staticmethod - def _get_fig_points(points, img_ratio, bounds): - plt.scatter(points.T[0], points.T[1], - color='red', **Utils.PLOT_KWDS) - fig = plt.figure(num=1, figsize=( - 11, int(11*img_ratio)), dpi=80) - fig.canvas.set_window_title('Preview Map') - Utils.plt_setxy_lim(plt, bounds) - plt.tick_params(labelsize=10) - return fig - - @staticmethod - def _get_sel_preview(points, item, bounds): - """Returns plt map for item selection preview""" - img_ratio = Utils._get_img_ratio(bounds) - fig = Utils._get_fig_points(points, img_ratio, bounds) - plt.suptitle(item, fontsize=18, fontweight='bold') - return fig - - @staticmethod - def _get_cluster_preview( - points, sel_colors, item_text, bounds, mask_noisy, - cluster_distance, number_of_clusters, auto_select_clusters=None): - if auto_select_clusters is None: - auto_select_clusters = False - # create main cluster points map - plt.scatter(points.T[0], points.T[1], - c=sel_colors, **Utils.PLOT_KWDS) - img_ratio = Utils._get_img_ratio(bounds) - fig1 = plt.figure(num=1, figsize=( - 11, int(11*img_ratio)), dpi=80) - fig1.canvas.set_window_title('Cluster Preview') - Utils._set_plt_suptitle_st(plt, item_text) - dist_text = '' - if auto_select_clusters is False: - dist_text = '@ ' + str(cluster_distance) + 'm' - plt.title(f'Cluster Preview {dist_text}', - fontsize=12, loc='center') - # xmax = fig1.get_xlim()[1] - # ymax = fig1.get_ylim()[1] - noisy_txt = '{} / {}'.format(mask_noisy.sum(), len(mask_noisy)) - plt.text(bounds.lim_lng_max, - bounds.lim_lat_max, - f'{number_of_clusters} Clusters (Noise: {noisy_txt})', - fontsize=10, horizontalalignment='right', - verticalalignment='top', fontweight='bold') - # set plotting bounds - Utils.plt_setxy_lim(plt, bounds) - Utils.set_plt_tick_params(plt) - return fig1 - - @staticmethod - def set_plt_tick_params(plt): - """Sets common plt tick params""" - plt.tick_params(labelsize=10) - - @staticmethod - def _set_plt_suptitle_st(plt, title: str): - """Set title of plt""" - plt.suptitle(title, - fontsize=18, fontweight='bold')