[= Personal - House Prices Command: prices.bat =] [= note: the Source feature is not supported in the free version =] Source 'pricesup.grs'; [= Const Time MinDate = '2018-07-01'; =] Const Time MinDate = '2019-01-01'; [= make all layout information global =] Const Number CanvasWidth = Canvas.width; Const Number CanvasHeight = Canvas.height; Const Number HorzMargin = 70; Const Number TopMargin = 20; Const Number XLabelHeight = 40; Number ChartTop, ChartHeight; Number KeyTop, KeyHeight = 60; Const Number DotSize = 8; [= diameter of dot =] [= set the minimum year for the display =] Time MaxDate = MinDate; [= actual value will be calculated in setup =] Number MinPrice = 150, MaxPrice = 150; Number XScale, YScale; Type TResults = { Number rooms, Number count, Number total }; Type TResultArr = TResults[1 To MaxRooms]; TResultArr Results = [ {1, 0, 0}, {2, 0, 0}, {3, 0, 0}, {4, 0, 0}, {5, 0, 0} ]; Logical Monochrome; Program(Logical mono = False) TPriceArr pricelist; Begin [= save the monochrom status =] Monochrome := mono; [= read in the master data file =] pricelist := ReadData("EXAMPLES:/prices.txt", "EXAMPLES:/pricefmt.txt"); If Not Setup(pricelist) Then Output "No data"; Return; EndIf; If Not Analyse(pricelist, Results) Then Output "Analysis Failed"; Return; EndIf; Heading(); MainChart(pricelist) => {HorzMargin, ChartTop + ChartHeight}; Key(Results) => {HorzMargin, KeyTop}; End; [= HEADING ------- =] Shape Heading() TFont headfont = Font { size -> 32 }; Begin TextBlock("House Prices in Mytown", halign -> Centre) => { CanvasWidth // 2, 16 }, headfont; TextBlock("Semi-detached, Freehold", halign -> Centre) => { CanvasWidth // 2, 48 }; End; [= MAIN CHART ---------- Plot the main chart for the data. =] Shape MainChart(Ref TPriceArr pricelist) TPen gridpen = Pen { colour -> { 50, 50,50} }; Begin XAxis() => gridpen; YAxis() => gridpen; Over pricelist As rec Do PlotSale(rec.rooms) => PlotPoint(rec.selldate, rec.price); EndOver; End; [= PLOT SALE --------- Plot a sale on the chart. =] Shape PlotSale(Number rooms) Saved TPen dotpen; Number current = -1; Begin If Monochrome Then Select rooms From Case 1 Do Plot1Room(); Case 2 Do Plot2Rooms(); Case 3 Do PlotManyRooms(rooms, Solid); Otherwise PlotManyRooms(rooms, Hollow); EndSelect; Else [= see if different roomcount =] If rooms <> current Then dotpen := Pen { width -> DotSize, colour -> RoomColours[rooms] }; current := rooms; EndIf; Dot() => dotpen; EndIf; End; [= PLOT 1 ROOM ------------ Special routine for plotting 1 rooms. Do a grey filled circle. =] Shape Plot1Room() Begin Circle(DotSize//2, Hollow); End; [= PLOT 2 ROOMS ----------- Special routine for two room house. Simple cross. =] Shape Plot2Rooms() Begin Line( {-DotSize // 2, 0}, {DotSize // 2, 0} ); Line( {0, -DotSize//2}, {0, DotSize//2} ); End; [= PLOT MANY ROOMS --------------- General routine for more than 2 rooms. =] Shape PlotManyRooms(Number rooms, EFillMode fill) PointArray points; Number i, theta; Begin For i From 0 To rooms - 1 Do theta := 90 - 360 / rooms * i; points += { DotSize / 2 * Cos(theta), -DotSize / 2 * Sin(theta) }; EndFor; Polygon(points, fill); End; [= ANALYSE ------- Analyse to data to allow averages to be derived. =] Function Logical Analyse(Ref TPriceArr pricelist, Ref TResultArr results) Begin Over pricelist As rec Do If rec.rooms >= 1 And rec.rooms <= MaxRooms And rec.selldate >= MinDate Then [= add to appropriate entry =] results[rec.rooms].total += rec.price; results[rec.rooms].count += 1; EndIf; EndOver; Return True; End; [= SETUP ----- Set everything up. =] Function Logical Setup(Ref TPriceArr pricelist) Begin [= check there is data =] If ArrayHigh(pricelist, 1) == Null Then Return False; EndIf; Over pricelist As rec Do [= check for higher price =] If rec.price > MaxPrice Then MaxPrice := rec.price; EndIf; [= check for later date =] If rec.selldate > MaxDate Then MaxDate := rec.selldate; EndIf; EndOver; [= fudge for now =] ChartTop := 80; ChartHeight := CanvasHeight - ChartTop - KeyHeight - XLabelHeight; KeyTop := ChartTop + ChartHeight + XLabelHeight; [= calculate the X scale =] XScale := ( CanvasWidth - 2 * HorzMargin ) / ( DiffDays(MinDate, MaxDate) + 1 ); [= calculate the Y scale - TEMP =] YScale := ChartHeight / ( MaxPrice - MinPrice ); Return True; End; [= KEY --- =] Shape Key(TResultArr results) Number hstep = ( CanvasWidth - 2 * HorzMargin ) / MaxRooms; Begin Over results As res Do KeyValue(res) => { ( res.rooms - 1 ) * hstep, 0}; EndOver; End; [= KEY VALUE --------- Do the key for one key. =] Shape KeyValue(Const Ref TResults res) Const Number hoff = 10, vstep = 20; TPen dotpen = Pen { width -> DotSize, colour -> RoomColours[res.rooms] }; Number average; Text label; Begin [= Dot() => dotpen; =] PlotSale(res.rooms); label := Format(res.rooms) + " bedroom"; TextBlock(label, valign-> Centre) => { hoff, 0 }; label := Format(res.count) + " sold"; TextBlock(label, valign-> Centre) => { hoff, vstep }; If res.count > 0 Then average := Round(res.total / res.count, Down); label := "£" + Format( average ) + "K average"; TextBlock(label, valign-> Centre) => { hoff, 2 * vstep }; EndIf; End; [= X AXIS ------ =] Shape XAxis() Number year, quarter; Time date; Point start; Text label; Begin [= get the start quarer =] year := GetYear(MinDate); quarter := ( GetMonth(MinDate) - 1 ) // 3; date := MinDate; While date < MaxDate Do start := PlotPoint(date, MinPrice); start.y += 10; Line(start, PlotPoint(date, MaxPrice)); start.x += 10; label := Format(year) + "Q" + Format(quarter + 1); TextBlock(label) => start; [= advance date =] If quarter == 3 Then year += 1; quarter := 0; Else quarter += 1; EndIf; date := MakeDate(year, quarter * 3 + 1, 1); EndWhile; End; [= Y AXIS ------ =] Shape YAxis() Number price; Point start; Text label; Begin price := MinPrice; While price < MaxPrice Do start := PlotPoint(MinDate, price); start.x -= 10; Line(start, PlotPoint(MaxDate, price)); label := "£" + Format(price) + "K"; TextBlock(label, halign -> Right, valign -> Centre) => start; price += 25; EndWhile; End; [= PLOT POINT ---------- Calculate the coordinate of the point to plot. Relative to the chart origin. =] Function Point PlotPoint(Time selldate, Number price) Begin Return { DiffDays(MinDate, selldate) * XScale, -(price - MinPrice ) * YScale }; End;