How to build a Full 2x2 Matrix Grid Layout as a Confusion Matrix: Displays the matrix sequence with True Positives (TP) and False Negatives (FN) in Row 1, and False Positives (FP) and True Negatives (TN) in Row 2.

Script Features
4 Real-Time Trackbars (TTrackBar): Independently adjust True Positives (TP), False Negatives (FN), False Positives (FP), and True Negatives (TN) from 0 to 20.
Instant Recalculation: Moving any slider dynamically updates the Accuracy, Precision, Recall, Specificity, and F1-Score in the log window.
Live Heatmap Canvas: Re-draws the 2x2 grid on TImage.Canvas with cell shading proportional to counts.

https://raw.githack.com/maxkleiner/maXbox5/main/assets/machinelearning55.htm

The Code

The Code

type TMatrix2x2 = array[0..1] of array[0..1] of Integer; TTStringArray = array[0..1] of string; var Form1: TForm; ImgCanvas: TImage; MemoLog: TMemo; BtnCalc: TButton; EdtTP, EdtFN, EdtFP, EdtTN: TEdit; LblTP, LblFN, LblFP, LblTN: TLabel; ClassNames: TTStringArray; procedure CalculateAndDrawMatrix(Sender: TObject); var CM: TMatrix2x2; r, c, TotalSamples, Correct: Integer; Acc, Prec, rRec, F1, Spec: Double; W, H, CellW, CellH, OffX, OffY, rVal, MaxVal: Integer; ColorVal: Byte; begin // Class Names  ClassNames[0]:= 'Known'; ClassNames[1]:= 'Don''t Know'; // Populate 2x2 Confusion Matrix Counts based on Person A's 9-Person Scenario:  // TP (Known -> Known) = 3  // FN (Known -> Don't Know) = 2  // FP (Don't Know -> Known) = 1  // TN (Don't Know -> Don't Know) = 3  CM[0][0]:= 3; // TP  CM[0][1]:= 2; // FN  CM[1][0]:= 1; // FP  CM[1][1]:= 3; // TN  TotalSamples:= CM[0][0] + CM[0][1] + CM[1][0] + CM[1][1]; // 9  Correct:= CM[0][0] + CM[1][1]; // 6  Acc:= (Correct * 100.0) / TotalSamples; // 66.67%  // Metrics for "Known" (Positive class)  if (CM[0][0] + CM[1][0]) > 0 then Prec:= (CM[0][0] * 100.0) / (CM[0][0] + CM[1][0]) else Prec:= 0.0; // 3/4 = 75.0%  if (CM[0][0] + CM[0][1]) > 0 then rRec:= (CM[0][0] * 100.0) / (CM[0][0] + CM[0][1]) else rRec:= 0.0; // 3/5 = 60.0%  if (CM[1][1] + CM[1][0]) > 0 then Spec:= (CM[1][1] * 100.0) / (CM[1][1] + CM[1][0]) else Spec:= 0.0; // 3/4 = 75.0%  if (Prec + rRec) > 0 then F1:= (2.0 * Prec * rRec) / (Prec + rRec) else F1:= 0.0; // 66.67%  // Memo Log Output  MemoLog.Clear; MemoLog.Lines.Add('=== PERSON CONFUSION MATRIX ==='); MemoLog.Lines.Add('Total People Evaluated : '+ IntToStr(TotalSamples)); MemoLog.Lines.Add('Overall Accuracy : '+ FormatFloat('0.00%', Acc)); MemoLog.Lines.Add(''); MemoLog.Lines.Add('ACTUAL \ PREDICTED | Known | Don''t Know | Total'); MemoLog.Lines.Add('----------------------------'); MemoLog.Lines.Add(Format('%-16s | %2d | %2d | %2d',[ClassNames[0],CM[0][0],CM[0][1],CM[0][0]+CM[0][1]])); MemoLog.Lines.Add(Format('%-16s | %2d | %2d | %2d',[ClassNames[1],CM[1][0],CM[1][1],CM[1][0]+CM[1][1]])); MemoLog.Lines.Add('----------------------------'); MemoLog.Lines.Add(Format('%-16s | %2d | %2d | %2d',['Total Predicted',CM[0][0]+CM[1][0],CM[0][1]+CM[1][1],TotalSamples])); MemoLog.Lines.Add(''); MemoLog.Lines.Add('--- KEY EVALUATION METRICS ---'); MemoLog.Lines.Add('Precision (Known) : ' + FormatFloat('0.00%', Prec) + ' (3/4)'); MemoLog.Lines.Add('Recall / Sensitivity: ' + FormatFloat('0.00%', rRec) + ' (3/5)'); MemoLog.Lines.Add('Specificity : ' + FormatFloat('0.00%', Spec) + ' (3/4)'); MemoLog.Lines.Add('F1-Score : ' + FormatFloat('0.00%', F1)); // Render Canvas  W:= ImgCanvas.Width; H:= ImgCanvas.Height; ImgCanvas.Canvas.Brush.Color:= $DCF8FF //$3F85CD; //$ADDEFF; clWhite;  ImgCanvas.Canvas.FillRect(Rect(0, 0, W, H)); MaxVal:= 3; OffX:= 120; OffY:= 110; CellW:= 100; CellH:= 100; // Title and Axis Labels  ImgCanvas.Canvas.Font.Name:= 'Arial'; ImgCanvas.Canvas.Font.Size:= 14; ImgCanvas.Canvas.Font.Style2:= fsBold2; ImgCanvas.Canvas.Font.Color:= clBlack; ImgCanvas.Canvas.TextOut(OffX - 2, 35, 'Predicted Status'); ImgCanvas.Canvas.TextOut(10, OffY + 80, 'Actual'); // Column Headers  ImgCanvas.Canvas.Font.Size:= 9; ImgCanvas.Canvas.TextOut(OffX - 1, OffY - 30, ClassNames[0]); ImgCanvas.Canvas.TextOut(OffX + CellW - 1, OffY - 30, ClassNames[1]); ImgCanvas.Canvas.TextOut(OffX , OffY + 220, 'Sample Size: 9'); // Cells  for r:= 0 to 1 do begin ImgCanvas.Canvas.TextOut(10, OffY + r * CellH + 40, ClassNames[r]); for c:= 0 to 1 do begin rVal:= CM[r][ c]; // Color intensity based on count  ColorVal:= 255 - Round((rVal / MaxVal) * 160); // Diagonal cells (TP, TN) green/teal, Off-diagonal (FN, FP) orange/amber  if r = c then ImgCanvas.Canvas.Brush.Color:= RGB(ColorVal, 255, ColorVal) else ImgCanvas.Canvas.Brush.Color:= RGB(255, 230, ColorVal); ImgCanvas.Canvas.Pen.Color:= clNavy; ImgCanvas.Canvas.Pen.Width:= 2; ImgCanvas.Canvas.Rectangle(OffX+ c * CellW, OffY+ r * CellH, OffX+ (c+ 1) * CellW, OffY+ (r+ 1) * CellH); // Value & Label text inside cell  ImgCanvas.Canvas.Font.Size:= 16; ImgCanvas.Canvas.Font.Style2:= fsBold2; ImgCanvas.Canvas.Font.Color:= clBlack; ImgCanvas.Canvas.Brush.Style:= bsClear; ImgCanvas.Canvas.TextOut(OffX + c * CellW + 42, OffY + r * CellH + 28, IntToStr(rVal)); ImgCanvas.Canvas.Font.Size:= 8; ImgCanvas.Canvas.Font.Style2:= fsNormal; if (r=0) and (c=0) then ImgCanvas.Canvas.TextOut(OffX + c * CellW + 15, OffY + r * CellH + 65, '(True Pos)') else if (r=0) and (c=1) then ImgCanvas.Canvas.TextOut(OffX+ c * CellW+ 10, OffY+ r * CellH+ 65, '(False Neg)') else if (r=1) and (c=0) then ImgCanvas.Canvas.TextOut(OffX+ c * CellW+ 10, OffY+ r * CellH+ 65, '(False Pos)') else if (r=1) and (c=1) then ImgCanvas.Canvas.TextOut(OffX+ c * CellW+ 15, OffY+ r * CellH+ 65, '(True Neg)'); ImgCanvas.Canvas.Brush.Style:= bsSolid; ImgCanvas.Canvas.Font.Size:= 9; ImgCanvas.Canvas.Font.Style2:= fsBold2; end; end; end;