-
Notifications
You must be signed in to change notification settings - Fork 0
Better date axes #68
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
phil-brad
wants to merge
7
commits into
master
Choose a base branch
from
feature/heatmap-date-ticks
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+70
−13
Open
Better date axes #68
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
eb30045
Change gridlines to dashed
phil-brad 9396ab2
Concise date time axes
phil-brad b025d30
Merge remote-tracking branch 'origin/master' into feature/grid-lines
phil-brad 0f1665a
Also format y ticks
phil-brad 498b2ae
revert gridlines commit to keep it separate
phil-brad 083a6cb
Apply suggestions from code review
phil-brad 77a7c47
Fix inverted y axis
phil-brad File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,7 @@ | |
| # along with this program; if not, write to the Free Software Foundation, | ||
| # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
| from io import BytesIO | ||
| import math | ||
| import numpy as np | ||
| from datetime import datetime | ||
|
|
||
|
|
@@ -23,6 +24,7 @@ | |
| from PyQt5.QtGui import QKeySequence, QImage | ||
|
|
||
| import matplotlib | ||
| import matplotlib.dates as mdates | ||
| from matplotlib.legend import DraggableLegend | ||
| from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas | ||
| from matplotlib.figure import Figure | ||
|
|
@@ -202,6 +204,7 @@ def __init__(self, | |
| self._setting_axis_limits = False | ||
|
|
||
| self.hasHiddenSeries = False | ||
| self._axes.grid(False) | ||
|
|
||
| enabledToolsChanged = pyqtSignal() | ||
| spanChanged = pyqtSignal(SpanModel) | ||
|
|
@@ -868,23 +871,31 @@ def _update_ticks(self): | |
| if not self.data: | ||
| return | ||
| if hasattr(self.data, 'x_labels'): | ||
| step = self.data.x_tick_interval if hasattr(self.data, 'x_tick_interval') else None | ||
| x_ticks, x_labels, x_ticks_rotation = self._get_labels(self.data.x_labels, step, horizontal=True) | ||
| self._axes.set_xticks(x_ticks) | ||
| if len(self.data.x_labels) > 0 and all(isinstance(x, (np.datetime64, pd.Timestamp)) for x in self.data.x_labels): | ||
| x_labels = self.data.x_labels | ||
| self._update_date_xticks(x_labels) | ||
| x_ticks_rotation = 0.0 | ||
| else: | ||
| step = self.data.x_tick_interval if hasattr(self.data, 'x_tick_interval') else None | ||
| x_ticks, x_labels, x_ticks_rotation = self._get_labels(self.data.x_labels, step, horizontal=True) | ||
| self._axes.set_xticks(x_ticks, x_labels) | ||
| if hasattr(self.data, 'x_ticks_rotation'): | ||
| rotation = x_ticks_rotation if np.isnan(self.data.x_ticks_rotation) else self.data.x_ticks_rotation | ||
| self._axes.set_xticklabels( | ||
| x_labels, rotation=rotation, ha='right' if rotation else 'center') | ||
| self._adjust_to_xticklabels_height(x_labels, rotation) | ||
| else: | ||
| self._axes.set_xticklabels(x_labels) | ||
| self._axes.xaxis.set_tick_params(labelrotation=rotation) | ||
| matplotlib.artist.setp(self._axes.get_xticklabels(), | ||
| horizontalalignment='right' if rotation else 'center') | ||
| self._adjust_to_xticklabels_height(self._axes.get_xticklabels(), rotation) | ||
|
|
||
| if hasattr(self.data, 'y_labels'): | ||
| step = self.data.y_tick_interval if hasattr(self.data, 'y_tick_interval') else None | ||
| y_ticks, y_labels, _ = self._get_labels(self.data.y_labels, step, horizontal=False) | ||
| self._axes.set_yticks(y_ticks) | ||
| self._axes.set_yticklabels(y_labels) | ||
| self._adjust_to_yticklabels_width(y_labels) | ||
| if len(self.data.y_labels) > 0 and all(isinstance(y, (np.datetime64, pd.Timestamp)) for y in self.data.y_labels): | ||
| y_labels = self.data.y_labels | ||
| self._update_date_yticks(y_labels) | ||
| else: | ||
| step = self.data.y_tick_interval if hasattr(self.data, 'y_tick_interval') else None | ||
| y_ticks, y_labels, _ = self._get_labels(self.data.y_labels, step, horizontal=False) | ||
| self._axes.set_yticks(y_ticks) | ||
| self._axes.set_yticklabels(y_labels) | ||
| self._adjust_to_yticklabels_width(y_labels) | ||
|
|
||
| def _adjust_to_yticklabels_width(self, labels): | ||
| sizes = self._divider.get_horizontal() | ||
|
|
@@ -919,6 +930,52 @@ def _adjust_to_xticklabels_height(self, labels, rotation=None): | |
| sizes[0] = Size.Fixed(height / self._figure.dpi) | ||
| self._divider.set_vertical(sizes) | ||
|
|
||
| def _update_date_yticks(self, y_labels): | ||
| x_extent, y_extent = self._get_xy_extents() | ||
| ipositions, tick_formats, axis_label = self._determine_date_ticks(y_labels, self.axes.yaxis, self.data.yAxisTitle, y_extent) | ||
| self._axes.set_yticks(ipositions, tick_formats) | ||
| self._axes.set_ylabel(axis_label) | ||
| self._adjust_to_yticklabels_width(tick_formats) | ||
|
|
||
| def _update_date_xticks(self, x_labels): | ||
| x_extent, y_extent = self._get_xy_extents() | ||
| ipositions, tick_formats, axis_label = self._determine_date_ticks(x_labels, self.axes.xaxis, self.data.xAxisTitle, x_extent) | ||
| self._axes.set_xticks(ipositions, tick_formats) | ||
| self._axes.set_xlabel(axis_label) | ||
|
|
||
| def _determine_date_ticks(self, labels, axis_obj, axis_title, extent): | ||
| e0, e1 = extent | ||
| imin, imax = max(0, math.floor(e0)), min(math.ceil(e1), len(labels) - 1) | ||
| # Convert all labels to date numbers for robust min/max handling | ||
| label_dates = [mdates.date2num(lbl) for lbl in labels] | ||
| date_num_min = min(label_dates) | ||
| date_num_max = max(label_dates) | ||
|
|
||
| locator = mdates.AutoDateLocator(maxticks=max(1, min(imax - imin, 25))) | ||
| offset_formats = ['', '%Y', '%Y-%b', '%Y-%b', '%Y-%m-%d', '%Y-%m-%d'] | ||
| formatter = mdates.ConciseDateFormatter(locator, | ||
| offset_formats=offset_formats) | ||
|
|
||
|
phil-brad marked this conversation as resolved.
|
||
| if date_num_max == date_num_min: | ||
| tick_vals = np.array([date_num_min]) | ||
| ipositions = np.array([(imin + imax) / 2.0]) | ||
| tick_formats = formatter.format_ticks(tick_vals) | ||
| axis_label = f'{axis_title} ({formatter.get_offset()})' if formatter.get_offset() else axis_title | ||
| return ipositions, tick_formats, axis_label | ||
|
|
||
| # Ensure correct order for tick_values | ||
| if label_dates[imin] <= label_dates[imax]: | ||
| tick_vals = locator.tick_values(labels[imin], labels[imax]) | ||
| ipositions = (len(labels) - 1) * (tick_vals - date_num_min) / (date_num_max - date_num_min) - 0.5 | ||
| else: | ||
| tick_vals = locator.tick_values(labels[imax], labels[imin]) | ||
| ipositions = (len(labels) - 1) * (tick_vals[::-1] - date_num_min) / (date_num_max - date_num_min) - 0.5 | ||
|
|
||
| tick_formats = formatter.format_ticks(tick_vals) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if |
||
| axis_label = f'{axis_title} ({formatter.get_offset()})' if formatter.get_offset() else axis_title | ||
| return ipositions, tick_formats, axis_label | ||
|
|
||
|
|
||
| def _get_labels(self, labels, step, horizontal=True): | ||
| (x0, x1), (y0, y1) = self._get_xy_extents() | ||
| start, end = (int(x0), int(x1)) if horizontal else (int(y0), int(y1)) | ||
|
|
||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.