@@ -906,3 +906,175 @@ def align(self, interval, stepSize):
906906 x2 = interval .maxValue ()
907907
908908 return qwtPowInterval (self .base (), QwtInterval (x1 , x2 ))
909+
910+
911+ class QwtDateTimeScaleEngine (QwtLinearScaleEngine ):
912+ """
913+ A scale engine for datetime scales that creates intelligent time-based tick intervals.
914+
915+ This engine calculates tick intervals that correspond to meaningful time units
916+ (seconds, minutes, hours, days, weeks, months, years) rather than arbitrary
917+ numerical spacing.
918+ """
919+
920+ # Time intervals in seconds
921+ TIME_INTERVALS = [
922+ 1 , # 1 second
923+ 5 , # 5 seconds
924+ 10 , # 10 seconds
925+ 15 , # 15 seconds
926+ 30 , # 30 seconds
927+ 60 , # 1 minute
928+ 2 * 60 , # 2 minutes
929+ 5 * 60 , # 5 minutes
930+ 10 * 60 , # 10 minutes
931+ 15 * 60 , # 15 minutes
932+ 30 * 60 , # 30 minutes
933+ 60 * 60 , # 1 hour
934+ 2 * 60 * 60 , # 2 hours
935+ 3 * 60 * 60 , # 3 hours
936+ 6 * 60 * 60 , # 6 hours
937+ 12 * 60 * 60 , # 12 hours
938+ 24 * 60 * 60 , # 1 day
939+ 2 * 24 * 60 * 60 , # 2 days
940+ 7 * 24 * 60 * 60 , # 1 week
941+ 2 * 7 * 24 * 60 * 60 , # 2 weeks
942+ 30 * 24 * 60 * 60 , # 1 month (approx)
943+ 3 * 30 * 24 * 60 * 60 , # 3 months (approx)
944+ 6 * 30 * 24 * 60 * 60 , # 6 months (approx)
945+ 365 * 24 * 60 * 60 , # 1 year (approx)
946+ ]
947+
948+ def __init__ (self , base = 10 ):
949+ super (QwtDateTimeScaleEngine , self ).__init__ (base )
950+
951+ def divideScale (self , x1 , x2 , maxMajorSteps , maxMinorSteps , stepSize = 0.0 ):
952+ """
953+ Calculate a scale division for a datetime interval
954+
955+ :param float x1: First interval limit (Unix timestamp)
956+ :param float x2: Second interval limit (Unix timestamp)
957+ :param int maxMajorSteps: Maximum for the number of major steps
958+ :param int maxMinorSteps: Maximum number of minor steps
959+ :param float stepSize: Step size. If stepSize == 0.0, calculates intelligent datetime step
960+ :return: Calculated scale division
961+ """
962+ interval = QwtInterval (x1 , x2 ).normalized ()
963+ if interval .width () <= 0 :
964+ return QwtScaleDiv ()
965+
966+ # If stepSize is provided and > 0, use parent implementation
967+ if stepSize > 0.0 :
968+ return super (QwtDateTimeScaleEngine , self ).divideScale (
969+ x1 , x2 , maxMajorSteps , maxMinorSteps , stepSize
970+ )
971+
972+ # Calculate intelligent datetime step size
973+ duration = interval .width () # Duration in seconds
974+
975+ # Find the best time interval for the given duration and max steps
976+ best_step = self ._find_best_time_step (duration , maxMajorSteps )
977+
978+ # Use the calculated datetime step
979+ scaleDiv = QwtScaleDiv ()
980+ if best_step > 0.0 :
981+ ticks = self .buildTicks (interval , best_step , maxMinorSteps )
982+ scaleDiv = QwtScaleDiv (interval , ticks )
983+
984+ if x1 > x2 :
985+ scaleDiv .invert ()
986+
987+ return scaleDiv
988+
989+ def _find_best_time_step (self , duration , max_steps ):
990+ """
991+ Find the best time interval step for the given duration and maximum steps.
992+
993+ :param float duration: Total duration in seconds
994+ :param int max_steps: Maximum number of major ticks
995+ :return: Best step size in seconds
996+ """
997+ if max_steps < 1 :
998+ max_steps = 1
999+
1000+ # Calculate the target step size
1001+ target_step = duration / max_steps
1002+
1003+ # Find the time interval that is closest to our target
1004+ best_step = self .TIME_INTERVALS [0 ]
1005+ min_error = abs (target_step - best_step )
1006+
1007+ for interval in self .TIME_INTERVALS :
1008+ error = abs (target_step - interval )
1009+ if error < min_error :
1010+ min_error = error
1011+ best_step = interval
1012+ # If the interval is getting much larger than target, stop
1013+ elif interval > target_step * 2 :
1014+ break
1015+
1016+ return float (best_step )
1017+
1018+ def buildMinorTicks (self , ticks , maxMinorSteps , stepSize ):
1019+ """
1020+ Calculate minor ticks for datetime intervals
1021+
1022+ :param list ticks: List of tick arrays
1023+ :param int maxMinorSteps: Maximum number of minor steps
1024+ :param float stepSize: Major tick step size
1025+ """
1026+ if maxMinorSteps < 1 :
1027+ return
1028+
1029+ # For datetime, create intelligent minor tick intervals
1030+ minor_step = self ._get_minor_step (stepSize , maxMinorSteps )
1031+
1032+ if minor_step <= 0 :
1033+ return
1034+
1035+ major_ticks = ticks [QwtScaleDiv .MajorTick ]
1036+ if len (major_ticks ) < 2 :
1037+ return
1038+
1039+ minor_ticks = []
1040+
1041+ # Generate minor ticks between each pair of major ticks
1042+ for i in range (len (major_ticks ) - 1 ):
1043+ start = major_ticks [i ]
1044+ end = major_ticks [i + 1 ]
1045+
1046+ # Add minor ticks between start and end
1047+ current = start + minor_step
1048+ while current < end :
1049+ minor_ticks .append (current )
1050+ current += minor_step
1051+
1052+ ticks [QwtScaleDiv .MinorTick ] = minor_ticks
1053+
1054+ def _get_minor_step (self , major_step , max_minor_steps ):
1055+ """
1056+ Calculate appropriate minor tick step size for datetime intervals
1057+
1058+ :param float major_step: Major tick step size in seconds
1059+ :param int max_minor_steps: Maximum number of minor steps
1060+ :return: Minor tick step size in seconds
1061+ """
1062+ # Define sensible minor tick divisions for different time scales
1063+ if major_step >= 365 * 24 * 60 * 60 : # 1 year or more
1064+ return 30 * 24 * 60 * 60 # 1 month
1065+ elif major_step >= 30 * 24 * 60 * 60 : # 1 month or more
1066+ return 7 * 24 * 60 * 60 # 1 week
1067+ elif major_step >= 7 * 24 * 60 * 60 : # 1 week or more
1068+ return 24 * 60 * 60 # 1 day
1069+ elif major_step >= 24 * 60 * 60 : # 1 day or more
1070+ return 6 * 60 * 60 # 6 hours
1071+ elif major_step >= 60 * 60 : # 1 hour or more
1072+ return 15 * 60 # 15 minutes
1073+ elif major_step >= 10 * 60 : # 10 minutes or more
1074+ return 2 * 60 # 2 minutes
1075+ elif major_step >= 60 : # 1 minute or more
1076+ return 15 # 15 seconds
1077+ elif major_step >= 10 : # 10 seconds or more
1078+ return 2 # 2 seconds
1079+ else : # Less than 10 seconds
1080+ return major_step / max (max_minor_steps , 2 )
0 commit comments