6

Background

A line is being drawn using MetaPost on an overlay, which is used as the background for a framed layer. The layer is embedded within a setups so that a dot's position on the timeline is recalculated each time. The calculation is based on the value of the current section's title text.

Problem

The horizontal bars at the each end of the timeline shifts a slight amount. Compare:

Chapter 1

Chapter 2

When shown overlapped, the difference is apparent, albeit subtle:

Shifted Bars

Code

The minimal working example is reasonably straightforward. The bulk of the code is taken up by parsing the number from the section title within \startuseMPgraphic.

\definepapersize[BookPaperSize][
  width=8in,
  height=6in,
]

\setuppapersize[BookPaperSize]

% Specify the width and height here because the inline figures need to
% maintain aspect ratio while setting a pleasing width.
\startsetups[BookIllustrationSetups]
  \setlayerframed[BookIllustrationLayer][
    frame=off,
    x=\zeropoint,
    y=1.25in,
    width=\makeupwidth,
    background=BookTimelineOverlay,
  ]{}
\stopsetups

\setupbackgrounds[page][
  background=BookIllustrationLayer,
  setups=BookIllustrationSetups
]

\definelayer[BookIllustrationLayer][
  width=\paperwidth,
  height=\paperheight,
]

\defineoverlay[BookTimelineOverlay][\useMPgraphic{BookTimelineGraphic}]

\startuseMPgraphic{BookTimelineGraphic}
  % Define constants
  numeric EVENT_BEGAN, TIME_OFFSET, PATH_THICKNESS;
  EVENT_BEGAN := 13799;
  TIME_OFFSET := 1.25in;
  PATH_THICKNESS := 2pt;

  % The book timeline title macro contains a number representing when the
  % event transpired and additional information.
  string sectionTitle, sectionTitleDigits;
  sectionTitle := "\getspecificstructuretitle{3}";
  sectionTitleDigits := "";

  % Extract only digits and decimals from the book timeline title macro.
  for i = 0 upto length( sectionTitle ):
    string sectionChar;
    sectionChar := substring( i, i + 1 ) of sectionTitle;

    % A space indicates that the number has no more digits.
    if sectionChar = " ":
      break;
    fi;

    % Concatentate all the digits together.
    if ((sectionChar >= "0") and (sectionChar <= "9")) or (sectionChar = "."):
      sectionTitleDigits := sectionTitleDigits & sectionChar;
    fi;
  endfor;

  % Convert the digits from a string to a numeric value.
  numeric eventTime;
  eventTime := 0;

  for i = scantokens( sectionTitleDigits ):
    eventTime := i;
  endfor;

  numeric eventX;
  eventX := 1 - (eventTime / EVENT_BEGAN);

  % Draw the line.
  draw (TIME_OFFSET, .5*\overlayheight) -- (\overlaywidth, .5*\overlayheight) \
    withpen pencircle scaled PATH_THICKNESS;

  % Draw starting |.
  draw (TIME_OFFSET, 0) -- (TIME_OFFSET, \overlayheight) \
    withpen pencircle scaled PATH_THICKNESS;

  % Draw ending |.
  draw (\overlaywidth, 0) -- (\overlaywidth, \overlayheight) \
    withpen pencircle scaled PATH_THICKNESS;

  % Draw the timeline's dot relative to the event on the timeline.
  filldraw fullcircle scaled 9 \
    shifted( \
      TIME_OFFSET + eventX * (\overlaywidth - TIME_OFFSET), .5*\overlayheight \
    );
\stopuseMPgraphic

\starttext
  \chapter[title={Inflation Theory},reference={inflation-theory}]
  \section[title={13,799 ± 0.021},reference={section}]
  \input ward

  \chapter[title={First Stars},reference={first-stars}]
  \section[title={13,689 ± 70},reference={section-1}]
  \input ward
\stoptext

Questions

I'm wondering:

  • Why does the timeline bar shift slightly?
  • How can the shifting be eliminated such that the timeline bars precisely overlap regardless of the timeline's dot's location?

Bonus question:

  • How can the code be simplified, especially parsing the title?

Example Output

Here's an example showing how the timeline graphic is used:

Preview

Resolution

With a few contextual tweaks, Henri's solution worked flawlessly:

  numeric wb, hb;
  wb := \overlaywidth - 1in;
  hb := .5in;

  draw image(
    draw image(
      % Draw the line.
      draw (0, .5*hb) -- (wb, .5*hb);

      % Draw starting |.
      draw (0, 0) -- (0, hb);

      % Draw ending |.
      draw (wb, 0) -- (wb, hb);
    )
    withpen pencircle scaled 2pt withcolor \MPcolor{\overlaylinecolor};

    draw image(
      % Draw the timeline's dot relative to the event on the timeline.
      filldraw fullcircle scaled 11 shifted( eventX * wb, .5*hb );
    )
    withcolor \MPcolor{BookTimelineColour};
  )
  shifted( .55in, OverlayHeight - .25 * hb );
  • 2
    Why don't you draw the rule as part of \setuphead? Why do you try use \ for line continuation? MetaPost scans until a semicolon and \ is actually a TeX control sequence which expands to a blank space. – Henri Menke Apr 28 at 7:30
5

The placement of the dot modifies the bounding box. The most ad-hoc solution to this problem is

  1. Save the bounding box before drawing the dot
  2. Draw the dot
  3. Restore the bounding box without dot

The dot will then leak out of the bounding box but that is only a tiny bit.

\definepapersize[BookPaperSize][
  width=8in,
  height=6in,
]

\setuppapersize[BookPaperSize]

% Specify the width and height here because the inline figures need to
% maintain aspect ratio while setting a pleasing width.
\startsetups[BookIllustrationSetups]
  \setlayerframed[BookIllustrationLayer][
    frame=off,
    x=\zeropoint,
    y=1.25in,
    width=\makeupwidth,
    background=BookTimelineOverlay,
  ]{}
\stopsetups

\setupbackgrounds[page][
  background=BookIllustrationLayer,
  setups=BookIllustrationSetups
]

\definelayer[BookIllustrationLayer][
  width=\paperwidth,
  height=\paperheight,
]

\defineoverlay[BookTimelineOverlay][\useMPgraphic{BookTimelineGraphic}]

\startuseMPgraphic{BookTimelineGraphic}
  % Define constants
  numeric EVENT_BEGAN, TIME_OFFSET, PATH_THICKNESS;
  EVENT_BEGAN := 13799;
  TIME_OFFSET := 1.25in;
  PATH_THICKNESS := 2pt;

  % The book timeline title macro contains a number representing when the
  % event transpired and additional information.
  string sectionTitle, sectionTitleDigits;
  sectionTitle := "\getspecificstructuretitle{3}";
  sectionTitleDigits := "";

  % Extract only digits and decimals from the book timeline title macro.
  for i = 0 upto length( sectionTitle ):
    string sectionChar;
    sectionChar := substring( i, i + 1 ) of sectionTitle;

    % A space indicates that the number has no more digits.
    if sectionChar = " ":
      break;
    fi;

    % Concatentate all the digits together.
    if ((sectionChar >= "0") and (sectionChar <= "9")) or (sectionChar = "."):
      sectionTitleDigits := sectionTitleDigits & sectionChar;
    fi;
  endfor;

  % Convert the digits from a string to a numeric value.
  numeric eventTime;
  eventTime := 0;

  for i = scantokens( sectionTitleDigits ):
    eventTime := i;
  endfor;

  numeric eventX;
  eventX := 1 - (eventTime / EVENT_BEGAN);

  % Draw the line.
  draw (TIME_OFFSET, .5*\overlayheight) -- (\overlaywidth, .5*\overlayheight)
    withpen pencircle scaled PATH_THICKNESS;

  % Draw starting |.
  draw (TIME_OFFSET, 0) -- (TIME_OFFSET, \overlayheight)
    withpen pencircle scaled PATH_THICKNESS;

  % Draw ending |.
  draw (\overlaywidth, 0) -- (\overlaywidth, \overlayheight)
    withpen pencircle scaled PATH_THICKNESS;

  % The dot will change the size of bounding box, so we will just save the
  % bounding box without the dot for later
  path p ; p := bbox currentpicture ;

  % Draw the timeline's dot relative to the event on the timeline.
  filldraw fullcircle scaled 9
    shifted(
      TIME_OFFSET + eventX * (\overlaywidth - TIME_OFFSET), .5*\overlayheight
    );

  % Restore the bounding box without dot
  setbounds currentpicture to p ;
\stopuseMPgraphic

\starttext
  \chapter[title={Inflation Theory},reference={inflation-theory}]
  \section[title={13,799 ± 0.021},reference={section}]
  \input ward

  \chapter[title={First Stars},reference={first-stars}]
  \section[title={13,689 ± 70},reference={section-1}]
  \input ward
\stoptext

Here is a little shorter version of your code, using Lua to parse the chapter title and using the MetaPost drawing as a background for the text layer of the page directly.

\definepapersize
  [BookPaperSize]
  [width=8in,
   height=6in]

\setuppapersize[BookPaperSize]

\defineoverlay
  [BookTimelineOverlay]
  [\useMPgraphic{BookTimelineGraphic}]

\setupbackgrounds
  [text]
  [background=BookTimelineOverlay]


\startluacode
function userdata.parsetitle(title)
    local a, b = string.match(title, "(%d+),(%d+)")
    return table.concat({ a, b }, "")
end
\stopluacode

\startuseMPgraphic{BookTimelineGraphic}
  % Define constants
  numeric EVENT_BEGAN;
  EVENT_BEGAN := 13799;

  % Convert the digits from a string to a numeric value.
  numeric eventX;
  eventX := lua("mp.print(userdata.parsetitle('\getspecificstructuretitle{3}'))") ;
  eventX := 1 - (eventX / EVENT_BEGAN);

  numeric wb, hb ; wb := 3.33in ; hb := .33in ;
  pickup pencircle scaled 2pt;
  picture p ; p := image(
      % Draw the line.
      draw (0, .5*hb) -- (wb, .5*hb) ;
      % Draw starting |.
      draw (0, 0) -- (0, hb) ;
      % Draw ending |.
      draw (wb, 0) -- (wb, hb) ;
      % Draw the timeline's dot relative to the event on the timeline.
      filldraw fullcircle scaled 9 shifted (eventX * wb, .5*hb);
  );

  numeric h ; h := OverlayHeight ;
  draw p shifted (-.33in, h - hb - .33in);

  setbounds currentpicture to OverlayBox ;
\stopuseMPgraphic

\starttext
  \chapter[title={Inflation Theory},reference={inflation-theory}]
  \section[title={13,799 ± 0.021},reference={section}]
  \input ward

  \chapter[title={First Stars},reference={first-stars}]
  \section[title={13,689 ± 70},reference={section-1}]
  \input ward
\stoptext
  • The userdata.parsetitle(title) wouldn't compile in my code, probably because I'm not using \setupbackgrounds[text][background=BookTimelineOverlay]. Rather, \startsetups[BookIllustrationSetups] combines two frames: a figure and the timeline overlay. Nonetheless, this solves the shifting problem flawlessly. – Dave Jarvis Apr 28 at 19:46
3

Henri has already answered how to fix the shift. I am going to answer your bonus question of how to simplify the code. For that, I'll use the visualcounter module.

First, I define a new visualcounter called progressmarker:

\usemodule[visualcounter]

\definevisualcounter
  [progressmarker]
  [         
    mp=visualcounter::progressmarker,
    width=\the\dimexpr\textwidth/2\relax,
    height=4ExHeight,
    rulethickness=2bp,
    mpsetups=visualcounter::markers:circle,
  ]

\startuseMPgraphic {visualcounter::progressmarker}
  begingroup;

  \includeMPgraphic{visualcounter::initialization}
  \includeMPgraphic{\visualcounterparameter{mpsetups}}

  shift := (current_counter-1)/(last_counter-1);

  newpath boundary;
  boundary := (0,-height/2) -- (width,-height/2) -- (width,height/2) -- (0, height/2) -- cycle;

  newpath pastline;
  pastline := (0, 0) -- (shift*width, 0);

  newpath futureline;
  futureline := (shift*width, 0) -- (width, 0);

  drawoptions(withpen pencircle scaled rulethickness);
  linecap := butt;

  draw leftboundary   boundary withcolor past_color;
  draw rightboundary  boundary withcolor future_color;
  draw pastline                withcolor past_color;
  draw futureline              withcolor future_color;
  drawoptions();

  show_marker(height/2, shift*width, active_color);

  setbounds currentpicture to boundary ;

  endgroup;
\stopuseMPgraphic

This can be used as follows:

\starttext
\startTEXpage[offset=2mm]
\usevisualcounter[n=4, last=10]{progressmarker}
\stopTEXpage
\stoptext

which gives:

enter image description here

The colors a bit off, because it is using the default color palette for visualcounter. To get a single solid color, we change the palette:

\definepalet[blue][past=blue, active=blue, future=blue]
\setupvisualcounter[progressmarker][palette=blue]

which gives:

enter image description here

So, all that is needed is to plug this into the section counter. This can be done using:

\definevisualcounter
  [visualsection]
  [progressmarker]
  [
    n={\somenamedheadnumber{section}{current}},
    last={\somenamedheadnumber{section}{last}},
  ]

\setuphead[section]
          [after={\usevisualcounter{visualsection}}]

\starttext
\startTEXpage[offset=2mm]
  \startsection[title=Inflation Theory]
    \input ward
  \stopsection
  \startsection[title=First Starts]
    \input ward
  \stopsection
  \startsection[title=Second Starts]
    \input ward
  \stopsection
\stopTEXpage
\stoptext

which gives enter image description here

  • Thanks for this! A lot of great ideas. The counter, to be clear, isn't linear nor related to the section number. Some of the values are 13799, 13689, 13403, 6900, 4568.2, 3496, 2191, 530, etc. These values are included in section titles. I was wondering about an efficient way to parse the section title string of "4,568.2" into the numeric data type value of 4568.2 to calculate the location of the dot (e.g., 4568.2 / 13799). – Dave Jarvis Apr 28 at 17:52
  • Using \usevisualcounter[n=4568.2, last=13799]{progressmarker} will work. To parse a string to number (in Metapost), you can use scantokens. For example u := scantokens("4326.2"); will set u to be the number 4326.2. – Aditya Apr 28 at 21:20
  • Nice! I noticed that scantokens cannot parse a number with commas, extra spaces, or ± symbols. Parsing was possible using the for i = 0 upto length( sectionTitle ): loop, though. Not sure how to reconcile scantokens with the section title -- not that it matters much as I've managed to get Henri's suggestions to work. Thanks, Aditya, your answer is filled with many useful techniques! – Dave Jarvis Apr 28 at 22:33
  • 1
    I am not sure how you want them to be parsed (simply ignored?). There is always the option of using luatex (lpeg) to do the parsing and pass the result back to metapost (see the metafun manual for details). If this remains an issue, then I would suggest to start a new question on this. – Aditya Apr 28 at 23:21
  • The section title (e.g., 13,799 ± 0.021 Ma or 4,500 to 3,500 Ma) is the source of truth for the numbers. Only the first number in the title matters as it relates the dot's position. The parser I wrote works and I was curious if there was a simpler way. Henri has a simpler way that worked for me standalone, but I couldn't make it work within my document. Not a huge deal. – Dave Jarvis Apr 29 at 1:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.