Skip to content
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

Date axis item #1154

Merged
merged 58 commits into from Apr 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
da8c53d
Add DateAxisItem
3rdcycle Jun 18, 2014
b21dfc2
Change style to camelCase
3rdcycle Jun 20, 2014
3816f4d
Fix missing first tick for negative timestamps
3rdcycle Jun 20, 2014
d627e39
Add ms precision, auto skipping
3rdcycle Jun 20, 2014
83f53de
fixes suggested by @goetzc
Apr 7, 2020
606cae6
workaround for negative argument to utcfromtimestamp on windows
Apr 7, 2020
41f9e4f
attachToPlotItem method
Apr 7, 2020
4b3199c
default date axis orientation
Apr 7, 2020
4c2b865
Use new DateAxisItem in Plot Customization example
2xB Apr 8, 2020
852f369
Merge pull request #1 from 2xB/2xb-date-axis-item-patch
axil Apr 8, 2020
3ac98c2
attachToPlotItem bugfix
Apr 8, 2020
61222db
examples of DateAxisItem
Apr 8, 2020
c856f2c
modified description of customPlot example
Apr 8, 2020
64120e8
added descriptions to the new examples, reformatted their code, inclu…
Apr 9, 2020
55d1749
typo
Apr 9, 2020
ab756bd
Refactored code for setting axis items into new function
2xB Apr 9, 2020
9e2e8d1
Fix string comparison with ==
2xB Apr 9, 2020
2f9b4f7
Doc: Slightly more text for DateAxisItem, small improvement for PlotItem
2xB Apr 9, 2020
a35c6b6
Make PlotWidget.setAxisItems official
2xB Apr 9, 2020
8a170f5
Fix typo in docstring
2xB Apr 9, 2020
e86bca7
Merge pull request #2 from 2xB/2xb-date-axis-item-patch2
axil Apr 10, 2020
75978ef
renamed an example
Apr 10, 2020
3958dc9
Merge branch 'develop' into date-axis-item
axil Apr 10, 2020
876b5a7
merge bug fix
Apr 10, 2020
b71cc0c
Revert "merge bug fix"
2xB Apr 10, 2020
a02af8e
Real bug fix
2xB Apr 10, 2020
625f1f4
Merge pull request #4 from 2xB/2xb-date-axis-item-patch3
axil Apr 11, 2020
24bcacd
support for dates upto -1e13..1e13
Apr 12, 2020
b58dfc2
Automatically limit DateAxisItem to a range from -1e12 to 1e12 years
2xB Apr 13, 2020
c5facf6
Also catch ValueErrors occuring on Linux before OverfloeErrors
2xB Apr 13, 2020
9259e12
Fix: Timestamp 0 corresponds to year 1970
2xB Apr 13, 2020
4703027
Fix: When zooming into extreme dates, OSError occurs
2xB Apr 13, 2020
db3fffd
Disable stepping below years for dates outside *_REGULAR_TIMESTAMP
2xB Apr 13, 2020
67a5624
Merge pull request #5 from 2xB/2xb-date-axis-item-patch4
axil Apr 13, 2020
a4f8216
Adapt zoom level sizes based on current font size and screen resolution
2xB Apr 13, 2020
58e32a5
Meaningful error meassage when adding axis to multiple PlotItems
2xB Apr 19, 2020
3e14971
Merge pull request #7 from 2xB/2xb-date-axis-item5-meaningfulerror
axil Apr 20, 2020
e29b500
setZoomLevelForDensity: Refactoring and calculating optimal spacing o…
2xB Apr 21, 2020
44dc901
DateTimeAxis Fix: 1970 shows when zooming far out
2xB Apr 21, 2020
ca69a5e
Refactoring: Make zoomLevels a customizable dict again
2xB Apr 21, 2020
021aa11
Merge pull request #6 from 2xB/2xb-date-axis-item-adaptive-zoom
axil Apr 21, 2020
0f6e9ef
updated the dateaxisitem example
Apr 21, 2020
15b8c7b
Fix: Get screen resolution in a way that also works for Qt 4
2xB Apr 21, 2020
3d1af34
Merge pull request #8 from 2xB/2xb-date-axis-item6-backwards-compatib…
axil Apr 21, 2020
aa6d0cc
DateAxisItem Fix: Also resolve time below 0.5 seconds
2xB Apr 21, 2020
577cc5f
unix line endings in examples
Apr 22, 2020
a494e9c
Merge pull request #9 from 2xB/2xb-date-axis-item7-zoomin-fix
axil Apr 22, 2020
7c37c02
DateTimeAxis Fix: For years < 1 and > 9999, stepping broke
2xB Apr 22, 2020
b600961
DateTimeAxis Fix: Zooming out too far causes infinite loop
2xB Apr 22, 2020
547cce8
improved second dateaxisitem example
Apr 22, 2020
98c0ed9
Merge pull request #10 from 2xB/2xb-date-axis-item9-fixlargeyears
axil Apr 22, 2020
7c817a0
1..9999 years limit
Apr 22, 2020
e11b8e2
DateTimeAxis: Use OrderedDict to stay compatible with Python < 3-6
2xB Apr 22, 2020
644d5e4
DateAxisItem: Use font height to determine spacing for vertical axes
2xB Apr 22, 2020
1c191ef
Merge pull request #11 from 2xB/2xb-date-axis-item10-multiplefixes
axil Apr 23, 2020
ad598fd
window title
Apr 24, 2020
d08850f
added dateaxisitem.rst
Apr 27, 2020
8facdc4
updated index.rst
Apr 27, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions doc/source/graphicsItems/dateaxisitem.rst
@@ -0,0 +1,8 @@
DateAxisItem
============

.. autoclass:: pyqtgraph.DateAxisItem
:members:

.. automethod:: pyqtgraph.DateAxisItem.__init__

1 change: 1 addition & 0 deletions doc/source/graphicsItems/index.rst
Expand Up @@ -43,3 +43,4 @@ Contents:
graphicsitem
uigraphicsitem
graphicswidgetanchor
dateaxisitem
1 change: 1 addition & 0 deletions doc/source/graphicsItems/make
Expand Up @@ -2,6 +2,7 @@ files = """ArrowItem
AxisItem
ButtonItem
CurvePoint
DateAxisItem
GradientEditorItem
GradientLegend
GraphicsLayout
Expand Down
33 changes: 33 additions & 0 deletions examples/DateAxisItem.py
@@ -0,0 +1,33 @@
"""
Demonstrates the usage of DateAxisItem to display properly-formatted
timestamps on x-axis which automatically adapt to current zoom level.

"""
import initExample ## Add path to library (just for examples; you do not need this)

import time
from datetime import datetime, timedelta

import numpy as np
import pyqtgraph as pg
from pyqtgraph.Qt import QtGui

app = QtGui.QApplication([])

# Create a plot with a date-time axis
w = pg.PlotWidget(axisItems = {'bottom': pg.DateAxisItem()})
w.showGrid(x=True, y=True)

# Plot sin(1/x^2) with timestamps in the last 100 years
now = time.time()
x = np.linspace(2*np.pi, 1000*2*np.pi, 8301)
w.plot(now-(2*np.pi/x)**2*100*np.pi*1e7, np.sin(x), symbol='o')

w.setWindowTitle('pyqtgraph example: DateAxisItem')
w.show()

## Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
app.exec_()
48 changes: 48 additions & 0 deletions examples/DateAxisItem_QtDesigner.py
@@ -0,0 +1,48 @@
"""
Demonstrates the usage of DateAxisItem in a layout created with Qt Designer.

The spotlight here is on the 'setAxisItems' method, without which
one would have to subclass plotWidget in order to attach a dateaxis to it.

"""
import initExample ## Add path to library (just for examples; you do not need this)

import sys
import time

import numpy as np
from PyQt5 import QtWidgets, QtCore, uic
import pyqtgraph as pg

pg.setConfigOption('background', 'w')
pg.setConfigOption('foreground', 'k')

BLUE = pg.mkPen('#1f77b4')

Design, _ = uic.loadUiType('DateAxisItem_QtDesigner.ui')

class ExampleApp(QtWidgets.QMainWindow, Design):
def __init__(self):
super().__init__()
self.setupUi(self)
now = time.time()
# Plot random values with timestamps in the last 6 months
timestamps = np.linspace(now - 6*30*24*3600, now, 100)
self.curve = self.plotWidget.plot(x=timestamps, y=np.random.rand(100),
symbol='o', symbolSize=5, pen=BLUE)
# 'o' circle 't' triangle 'd' diamond '+' plus 's' square
self.plotWidget.setAxisItems({'bottom': pg.DateAxisItem()})
self.plotWidget.showGrid(x=True, y=True)

app = QtWidgets.QApplication(sys.argv)
app.setStyle(QtWidgets.QStyleFactory.create('Fusion'))
app.setPalette(QtWidgets.QApplication.style().standardPalette())
window = ExampleApp()
window.setWindowTitle('pyqtgraph example: DateAxisItem_QtDesigner')
window.show()

## Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
app.exec_()
44 changes: 44 additions & 0 deletions examples/DateAxisItem_QtDesigner.ui
@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>536</width>
<height>381</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="PlotWidget" name="plotWidget"/>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>536</width>
<height>18</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<customwidgets>
<customwidget>
<class>PlotWidget</class>
<extends>QGraphicsView</extends>
<header>pyqtgraph</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
42 changes: 4 additions & 38 deletions examples/customPlot.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""
This example demonstrates the creation of a plot with a customized
AxisItem and ViewBox.
This example demonstrates the creation of a plot with
DateAxisItem and a customized ViewBox.
"""


Expand All @@ -12,40 +12,6 @@
import numpy as np
import time

class DateAxis(pg.AxisItem):
def tickStrings(self, values, scale, spacing):
strns = []
rng = max(values)-min(values)
#if rng < 120:
# return pg.AxisItem.tickStrings(self, values, scale, spacing)
if rng < 3600*24:
string = '%H:%M:%S'
label1 = '%b %d -'
label2 = ' %b %d, %Y'
elif rng >= 3600*24 and rng < 3600*24*30:
string = '%d'
label1 = '%b - '
label2 = '%b, %Y'
elif rng >= 3600*24*30 and rng < 3600*24*30*24:
string = '%b'
label1 = '%Y -'
label2 = ' %Y'
elif rng >=3600*24*30*24:
string = '%Y'
label1 = ''
label2 = ''
for x in values:
try:
strns.append(time.strftime(string, time.localtime(x)))
except ValueError: ## Windows can't handle dates before 1970
strns.append('')
try:
label = time.strftime(label1, time.localtime(min(values)))+time.strftime(label2, time.localtime(max(values)))
except ValueError:
label = ''
#self.setLabel(text=label)
return strns

class CustomViewBox(pg.ViewBox):
def __init__(self, *args, **kwds):
pg.ViewBox.__init__(self, *args, **kwds)
Expand All @@ -65,10 +31,10 @@ def mouseDragEvent(self, ev):

app = pg.mkQApp()

axis = DateAxis(orientation='bottom')
axis = pg.DateAxisItem(orientation='bottom')
vb = CustomViewBox()

pw = pg.PlotWidget(viewBox=vb, axisItems={'bottom': axis}, enableMenu=False, title="PlotItem with custom axis and ViewBox<br>Menu disabled, mouse behavior changed: left-drag to zoom, right-click to reset zoom")
pw = pg.PlotWidget(viewBox=vb, axisItems={'bottom': axis}, enableMenu=False, title="PlotItem with DateAxisItem and custom ViewBox<br>Menu disabled, mouse behavior changed: left-drag to zoom, right-click to reset zoom")
dates = np.arange(8) * (3600*24*356)
pw.plot(x=dates, y=[1,6,2,4,3,5,6,8], symbol='o')
pw.show()
Expand Down
1 change: 1 addition & 0 deletions examples/utils.py
Expand Up @@ -14,6 +14,7 @@
('Crosshair / Mouse interaction', 'crosshair.py'),
('Data Slicing', 'DataSlicing.py'),
('Plot Customization', 'customPlot.py'),
('Timestamps on x axis', 'DateAxisItem.py'),
('Image Analysis', 'imageAnalysis.py'),
('ViewBox Features', 'ViewBoxFeatures.py'),
('Dock widgets', 'dockarea.py'),
Expand Down
1 change: 1 addition & 0 deletions pyqtgraph/__init__.py
Expand Up @@ -219,6 +219,7 @@ def renamePyc(startDir):
from .graphicsItems.ArrowItem import *
from .graphicsItems.ImageItem import *
from .graphicsItems.AxisItem import *
from .graphicsItems.DateAxisItem import *
from .graphicsItems.LabelItem import *
from .graphicsItems.CurvePoint import *
from .graphicsItems.GraphicsWidgetAnchor import *
Expand Down
17 changes: 13 additions & 4 deletions pyqtgraph/graphicsItems/AxisItem.py
Expand Up @@ -507,20 +507,29 @@ def linkedView(self):

def linkToView(self, view):
"""Link this axis to a ViewBox, causing its displayed range to match the visible range of the view."""
oldView = self.linkedView()
self.unlinkFromView()

self._linkedView = weakref.ref(view)
if self.orientation in ['right', 'left']:
view.sigYRangeChanged.connect(self.linkedViewChanged)
else:
view.sigXRangeChanged.connect(self.linkedViewChanged)

view.sigResized.connect(self.linkedViewChanged)

def unlinkFromView(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wanted to point out #917 implements a similar feature

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! Since having a seperate method to unlink is the type of implementation you suggested over there, I'd suggest to just borrow the unit tests from over there. If that is agreed on, I'd do that, since unlinkFromView was from me.

"""Unlink this axis from a ViewBox."""
oldView = self.linkedView()
self._linkedView = None
if self.orientation in ['right', 'left']:
if oldView is not None:
oldView.sigYRangeChanged.disconnect(self.linkedViewChanged)
view.sigYRangeChanged.connect(self.linkedViewChanged)
else:
if oldView is not None:
oldView.sigXRangeChanged.disconnect(self.linkedViewChanged)
view.sigXRangeChanged.connect(self.linkedViewChanged)

if oldView is not None:
oldView.sigResized.disconnect(self.linkedViewChanged)
view.sigResized.connect(self.linkedViewChanged)

def linkedViewChanged(self, view, newRange=None):
if self.orientation in ['right', 'left']:
Expand Down