Message Boards Message Boards

[✓] Modify frametick units of timeseries plot with dynamic plotrange?

GROUPS:

This is a follow up question on an issue which has been discussed previously on another forum Discussion on StackExchange The StackExchange post raised an issue concerning example code from the MMA documentation on the dynamic IntervalSlider control. As desired the example code produces a timeseries plot with dynamic plotrange but fails to show gridlines and frameticks on the time axis.

This is the code copied and pasted from the MMA documentation (IntervalSlider/Applications):

DynamicModule[{data, xmin, xmax, ymin, ymax, int, w = 400, h = 30},
 data = FinancialData["IBM", "Jan. 1, 2004"];
 ymin = Min[Last /@ data]; 
 ymax = Max[Last /@ data]; {xmin, xmax} = 
  FromDate /@ data[[{1, -1}, 1]];
 Column[{
   Show[DateListPlot[data, ImageSize -> w, Joined -> True], 
    PlotRange -> {Dynamic[int], {ymin, ymax}}],
   IntervalSlider[Dynamic[int], {xmin, xmax, 1}, 
    ImageSize -> {400, 30}, MinIntervalSize -> 1],
   DateListPlot[data, Frame -> False, Axes -> False, 
    ImageSize -> {w, h}, AspectRatio -> h/w, Joined -> True, 
    Epilog -> {Opacity[0.5], Orange, 
      Dynamic[Rectangle @@ Thread[{int, {ymin, ymax}}]]}]}]]

As pointed out in the comments on the linked discussion, the gridlines and the frameticks on the time axis can be obtained by adding something like this to the list of plot options in the Show[DateListPlot[]] function

FrameTicks -> Automatic, GridLines -> Automatic

enter image description here

Alas, this solution still has one "flaw" I am struggling with: the frameticks on the time axis are given in AbsoluteTime format. That's not a very user friendly format and it would be much preferable to have a more easily readable format (for example the DateObject format).

How can I accomplish this?

I have tried several things, e.g. all kinds of more specific variations on the FrameTicks option or adding DateTicksFormat options, but anything other than the "Automatic" option gives me an error saying that the frametick option has not the right format ("A ticks specification in the value of FrameTicks should be None, Automatic, a function, or a list of ticks").

The StackExchange discussion also implies that the code gave a different (i.e. better) result on previous MMA versions. So, who knows, could this be a bug with vs11 ? ( I am using 11.1.1 on Win10)

POSTED BY: Eric L
Answer
7 months ago

Eric,

Your bug is that you need to initialize the variable "int". Evidently DateListPlot only picks the ticks based on the original first plot (before the dynamic) This code does what you want. I made an unrelated "improvement" by using a TimeSeries instead of raw data -- it makes the code a bit more readable and it will allow you to use all of the timeseries scaling and shifting options, i.e. if you want to later add a "shift" slider. For financial data you can also consider EventSeries so it does not interpolate between data. I added a date format which you can modify. A further improvement would be to dynamically change the number of ticks so that as you zoom in the ticks change. I'll try that next.

DynamicModule[{data, xmin, xmax, ymin, ymax, int, w = 400, h = 30}, 
 data = TimeSeries[FinancialData["IBM", "Jan. 1, 2004"]];
 ymin = Min[data["Values"]];
 ymax = Max[data["Values"]]; {xmin, xmax} = {data["FirstTime"], 
   data["LastTime"]};
 int = {xmin, xmax};
 Column[{Show[
    DateListPlot[data, ImageSize -> w, Joined -> True, 
     FrameTicks -> Automatic, 
     DateTicksFormat -> {"MonthNameShort", " ", "YearShort"}, 
     GridLines -> Automatic], 
    PlotRange -> {Dynamic[int], {0, 192.084754`}}], 
   IntervalSlider[Dynamic[int], {xmin, xmax, 1}, 
    ImageSize -> {400, 30}, MinIntervalSize -> 1], 
   DateListPlot[data, Frame -> False, Axes -> False, 
    ImageSize -> {w, h}, AspectRatio -> h/w, Joined -> True, 
    Epilog -> {Opacity[0.5], Orange, 
      Dynamic[Rectangle @@ Thread[{int, {ymin, ymax}}]]}]}]]

Regards

POSTED BY: Neil Singer
Answer
7 months ago

Here is the same code that will dynamically change the ticks as the sliders are moved. The key to making this work is to get rid of the Show[]. The DateListPlot must be in the Dynamic[] so it gets redrawn when the sliders change. Show[] seems to limit what can be redrawn and messes up the format of the ticks. I added a popup at the bottom for the number of ticks. This version (even if you delete the popup and fix the number of ticks) is much better than the previous one (above).

DynamicModule[{data, xmin, xmax, ymin, ymax, ticks, ticknum = 4, int, 
  w = 400, h = 30}, 
 data = TimeSeries[FinancialData["IBM", "Jan. 1, 2004"]];
 ymin = Min[data["Values"]];
 ymax = Max[data["Values"]]; {xmin, xmax} = {data["FirstTime"], 
   data["LastTime"]};
 int = {xmin, xmax};
 ticks = (xmax - xmin)/5*Range[4] + xmin;
 Column[{Dynamic[
    DateListPlot[data, ImageSize -> w, Joined -> True, 
     FrameTicks -> {Automatic, {ticks, None}}, 
     DateTicksFormat -> {"MonthNameShort", " ", "YearShort"}, 
     GridLines -> Automatic, PlotRange -> {int, Automatic}]], 
   IntervalSlider[
    Dynamic[int, {(int = #) &, (ticks = (int[[2]] - 
              int[[1]])/(ticknum + 1)*Range[ticknum] + 
          int[[1]]) &}], {xmin, xmax, 1}, ImageSize -> {400, 30}, 
    MinIntervalSize -> 1], 
   DateListPlot[data, Frame -> False, Axes -> False, 
    ImageSize -> {w, h}, AspectRatio -> h/w, Joined -> True, 
    Epilog -> {Opacity[0.5], Orange, 
      Dynamic[Rectangle @@ Thread[{int, {ymin, ymax}}]]}], 
   Row[{"Number of Ticks: ", 
     PopupMenu[Dynamic[ticknum], Range[10]]}]}]]

The only downside of the version above is that if you change the number of ticks in the popup, it does not redraw the plot -- you must move (or just click on) a slider. If you want the popup to instantly change the graph, you must force a variable inside the plot Dynamic[DateListPlot[...]] to change (such as the variable ticks). I added a function to the popup dynamic that will run after selection to change the variable ticks. This forces the redraw and I believe it has the behavior you are looking for.

DynamicModule[{data, xmin, xmax, ymin, ymax, ticks, ticknum = 4, int, 
  w = 400, h = 30}, 
 data = TimeSeries[FinancialData["IBM", "Jan. 1, 2004"]];
 ymin = Min[data["Values"]];
 ymax = Max[data["Values"]]; {xmin, xmax} = {data["FirstTime"], 
   data["LastTime"]};
 int = {xmin, xmax};
 ticks = (xmax - xmin)/5*Range[4] + xmin;
 Column[{Dynamic[
    DateListPlot[data, ImageSize -> w, Joined -> True, 
     FrameTicks -> {Automatic, {ticks, None}}, 
     DateTicksFormat -> {"MonthNameShort", " ", "YearShort"}, 
     GridLines -> Automatic, PlotRange -> {int, Automatic}]], 
   IntervalSlider[
    Dynamic[int, {(int = #) &, (ticks = (int[[2]] - 
              int[[1]])/(ticknum + 1)*Range[ticknum] + 
          int[[1]]) &}], {xmin, xmax, 1}, ImageSize -> {400, 30}, 
    MinIntervalSize -> 1], 
   DateListPlot[data, Frame -> False, Axes -> False, 
    ImageSize -> {w, h}, AspectRatio -> h/w, Joined -> True, 
    Epilog -> {Opacity[0.5], Orange, 
      Dynamic[Rectangle @@ Thread[{int, {ymin, ymax}}]]}], 
   Row[{"Number of Ticks: ", 
     PopupMenu[
      Dynamic[ticknum, {(ticknum = #) &, (ticks = (int[[2]] - 
                int[[1]])/(ticknum + 1)*Range[ticknum] + 
            int[[1]]) &}], Range[10]]}]}]]
POSTED BY: Neil Singer
Answer
7 months ago

Here is a gif of the code running:

enter image description here

POSTED BY: Neil Singer
Answer
7 months ago

Neil, your fantastic input is very much appreciated! Not only does it provide a great solution to the problems I have posted, it's also presented in a very instructive way. I did learn so much just by following your reasoning behind your problem solving choices ! ! Thank you!

Regards Eric

POSTED BY: Eric L
Answer
6 months ago

Glad I could help and I appreciate your note. I learned some interesting things about Dynamic by figuring this out! I have had related issues before that I did not fully understand.

Now that the ticks can be dynamically redrawn, you could further improve this by having the tick recomputation be a function and have it return some "smart" values so the number of ticks is chosen to give more intuitive spacing (for example, years when zoomed out, quarters when zoomed in, weeks when really zoomed in, etc.).

Regards,

Neil

POSTED BY: Neil Singer
Answer
6 months ago

Group Abstract Group Abstract