[= Demo - Duel Bar Command: duelbar.bat Purpose: Example of a bar chart with each bar showing two values. Here they are for average and gust wind speeds. In this example we are assuming that the page size is large enough for the plot: 800 x 800. =] [= set up some pens for the chart =] Const Colour RED = {100, 0, 0 }; Const Colour GREY = { 70, 70, 70 }; Private Const TPen GustPen = Pen { colour -> RED }; Private Const TPen GridPen = Pen { colour -> GREY }; [= set up some general layout constants =] Const Number LEFTGAP = 10; [= margin to the left of the plot =] Const Number BARWIDTH = 4; [= use a fixed bar width =] Const Number TICKLEN = 5; [= tick marks on the axes =] Const Number MAXSPEED = 60; [= assume a maximum speed =] Const Number PIXELPERMPH = 10; [= pixels per unit of wind speed ( mph) =] [= dummy value for the demo =] Const Time StartDate = '2018-10-03'; [= structure for derived layout information =] Type TLayout = { Point plotorigin, Number vtshift }; [= set up the types for the actual data =] Type RawWindRecord = { Number day, Number hour, Number average, Number gust }; Type RawWindArray = RawWindRecord[]; [= PROGRAM ------- =] Program() Number abshour; [= absolue hour from start =] TLayout layout; [= general layout information =] Begin [= set up the derived layout parameters =] If Not Setup(Font, layout) Then Output "Could not set up layout"; Return; EndIf; [= draw the main title for the chart =] MainTitle(); [= plot everything else =] DataPlot(layout) => layout.plotorigin; OnError [= probably occured reading the data =] Output "**** An error occured"; Output Status; End; [= MAIN TITLE ---------- Write the main title for the chart. The font is used only here so no point making it global. Could use Global constants for some of the values. =] Private Shape MainTitle() [= create a local font for the title =] TFont titlefont = Font { typeface -> "Lucida Sans Unicode", size -> 32 }; Begin [= write out the title centred on the page =] TextBlock("Sample Split Value Bar Chart", halign -> Centre, valign -> Top) => { Canvas.width / 2, 20 }, titlefont; End; [= DATA PLOT --------- This is the main plot. It include the grid, axes and labels. Origin: the axis intersection. =] Private Shape DataPlot(TLayout layout) RawWindArray rawdata; [= main data array =] Begin [= draw the grid system using the special pen =] With GridPen Do HorzGrid(); VertGrid(); EndWith; [= draw the axis title and value labels =] HorzLabels(); VertLabels(layout.vtshift); [= read in the data file =] rawdata := ReadData("EXAMPLES:/duelbar.txt"); [= draw all the data bars =] Over rawdata As entry Do Bar(entry); EndOver; End; [= BAR --- Draw the bar. It gust 40mph or more use the red pen. Note could calculate basepoint in the caller and make part of context. =] Private Shape Bar(RawWindRecord entry) Point basepoint; Number height; Begin [= work out location of the bottom of the bar =] basepoint := BasePoint(entry.day, entry.hour); basepoint.x += 2; [= work out the location of the top of the bar =] height := BarHeight(entry.average); [= negative height because going up the page =] Rectangle(BARWIDTH, -height, Filled) => basepoint; [= same for the gust bar =] height := BarHeight(entry.gust); [= decide which pen to use =] If entry.gust >= 40 Then Rectangle(BARWIDTH, -height) => basepoint, GustPen; Else Rectangle(BARWIDTH, -height) => basepoint; EndIf; End; [= HORZ GRID --------- Draw all the horiontal grid line - every 10mph. Add the tick too the start for the axis lables. The x range same for all. The y value recalculated for each. =] Private Shape HorzGrid() Number speed, height; Point start, end; Begin [= work out the locations =] end := BasePoint(4, 24); [= draw all the lines =] For speed From 0 To 60 Step 10 Do [= each line at a different verical position =] height := BarHeight(speed); Line( {-TICKLEN, -height}, {end.x, -height} ); EndFor; End; [= HORZ LABELS ----------- Label the horizontal axis. values actually match the vertical grid ( sorry, bit confusing ). =] Private Shape HorzLabels() Number day; Time workday = StartDate; Text label; Point location; Begin [= do each of the days =] For day From 1 To 4 Do [= just use the weekday name ( for example ) =] label := Format(workday, "W"); [= centre half way through the day =] location := BasePoint(day, 12); TextBlock(label, halign -> Centre) => location; [= go on to the next day =] workday := AddDays(workday, 1); EndFor; End; [= VERT GRID --------- Draw the vertical grid. Then far right line is effectly where a 5th day would start hence 1 To 5. =] Private Shape VertGrid() Number day, height; Point start, end; Begin [= all height same so do once before the loop =] height := BarHeight(MAXSPEED); For day From 1 To 5 Do start := BasePoint(day, 0); end := { start.x, start.y - height }; start.y += TICKLEN; Line(start, end); EndFor; End; [= VERT LABELS ----------- The vertical axis has a title drawn rotated and a series of formatted number for each horizontal line. =] Private Shape VertLabels(Number vtshift) Point location; Text label; Number speed; Begin location := { -vtshift, -BarHeight(MAXSPEED / 2 ) }; TextRotate("Wind/Gust Speed ( mph )", 270, Centre, Bottom) => location; For speed From 0 To MAXSPEED Step 10 Do [= format the speed as a Text =] label := Format(speed); [= change just the vertical position =] location := { -TICKLEN, -BarHeight(speed) }; TextBlock(label, halign -> Right, valign -> Centre ) => location; EndFor; End; [= SETUP ----- This will work out some general offsets base on the current font size. =] Private Function Logical Setup(TFont font, Ref TLayout layout) Point textsize; [= size of a dummy 88 value =] Begin [= deduce the size of the speed labels =] textsize := TextSize(" 88 ", font); layout.vtshift := textsize.x + TICKLEN; layout.plotorigin.x := LEFTGAP + textsize.y + layout.vtshift; layout.plotorigin.y := 700; [= for now =] [= assume all was OK =] Return True; OnError [= probably not relevant in this example - but generally a good idea =] Output "Could not set up layout"; Output Status; Return False; End; [= BAR HEIGHT ---------- Work out height of bar. =] Private Function Number BarHeight(Number speed) Begin [= in this case simply 10 pixels per MPH =] Return PIXELPERMPH * speed; End; [= BASE POINT ---------- Work out the coordinates of the base of the bar for a given time value - relative to axis origin. =] Private Function Point BasePoint(Number day, Number hour) Number abshour; Begin [= convert day and hour into absolute hour =] abshour := ( day - 1 ) * 24 + hour; [= scale and shift =] Return { ( BARWIDTH + 3 ) * abshour, 0 }; End;