import { PropositionalFormula } from '../../../types.js';
import { TruthTableFormatter } from './TruthTableFormatter.js';
/**
* SVG renderer for truth tables
* Handles SVG-specific formatting and visual styling
*/
export class TruthTableSVGRenderer {
private formatter: TruthTableFormatter;
// SVG configuration
private readonly cellWidth = 80;
private readonly cellHeight = 40;
private readonly headerHeight = 50;
private readonly footerHeight = 50;
private readonly padding = 10;
constructor() {
this.formatter = new TruthTableFormatter();
}
/**
* Render a truth table as SVG
* @param variables Variable names
* @param formulas Propositional formulas
* @param assignments Variable assignments
* @param results Evaluation results
* @param isArgument Whether this is an argument
* @param isArgumentValid Whether the argument is valid (if applicable)
* @returns SVG string
*/
render(
variables: string[],
formulas: PropositionalFormula[],
assignments: Map<string, boolean>[],
results: boolean[][],
isArgument: boolean,
isArgumentValid?: boolean
): string {
// Calculate dimensions
const numCols = variables.length + formulas.length + (isArgument ? 1 : 0);
const numRows = assignments.length;
const width = numCols * this.cellWidth + 2 * this.padding;
const height = this.headerHeight + numRows * this.cellHeight +
(isArgument ? this.footerHeight : 0) + 2 * this.padding;
// Build SVG
let svg = this.createSvgElement(width, height);
svg += this.renderBackground(width, height);
svg += this.renderHeaderCells(variables, formulas, isArgument);
svg += this.renderDataCells(variables, assignments, results, isArgument);
if (isArgument && isArgumentValid !== undefined) {
svg += this.renderFooter(numCols, numRows, isArgumentValid);
}
svg += '</svg>';
return svg;
}
private createSvgElement(width: number, height: number): string {
return `<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">`;
}
private renderBackground(width: number, height: number): string {
return `<rect x="0" y="0" width="${width}" height="${height}" fill="#f8f8f8" />`;
}
private renderHeaderCells(
variables: string[],
formulas: PropositionalFormula[],
isArgument: boolean
): string {
let svg = '';
const y = this.padding;
// Variable headers
variables.forEach((variable, colIndex) => {
const x = this.padding + colIndex * this.cellWidth;
svg += this.renderCell(x, y, this.cellWidth, this.headerHeight, '#e6e6e6');
svg += this.renderText(x + this.cellWidth/2, y + this.headerHeight/2, variable, 16);
});
// Formula headers
formulas.forEach((formula, formulaIndex) => {
const colIndex = variables.length + formulaIndex;
const x = this.padding + colIndex * this.cellWidth;
const isConclusion = isArgument && formulaIndex === formulas.length - 1;
const fill = isConclusion ? '#ffe6e6' : '#e6f3ff';
svg += this.renderCell(x, y, this.cellWidth, this.headerHeight, fill);
svg += this.renderText(
x + this.cellWidth/2,
y + this.headerHeight/2,
this.formatter.formatFormula(formula),
14
);
});
// Validity header
if (isArgument) {
const colIndex = variables.length + formulas.length;
const x = this.padding + colIndex * this.cellWidth;
svg += this.renderCell(x, y, this.cellWidth, this.headerHeight, '#f0f0f0');
svg += this.renderText(x + this.cellWidth/2, y + this.headerHeight/2, 'Valid?', 16);
}
return svg;
}
private renderDataCells(
variables: string[],
assignments: Map<string, boolean>[],
results: boolean[][],
isArgument: boolean
): string {
let svg = '';
assignments.forEach((assignment, rowIndex) => {
const y = this.padding + this.headerHeight + rowIndex * this.cellHeight;
// Variable values
variables.forEach((variable, colIndex) => {
const x = this.padding + colIndex * this.cellWidth;
const value = assignment.get(variable)!;
svg += this.renderCell(x, y, this.cellWidth, this.cellHeight, 'white');
svg += this.renderText(
x + this.cellWidth/2,
y + this.cellHeight/2,
this.formatter.formatBoolean(value),
16,
'bold'
);
});
// Formula results
const rowResults = results[rowIndex];
rowResults.forEach((result, formulaIndex) => {
const colIndex = variables.length + formulaIndex;
const x = this.padding + colIndex * this.cellWidth;
const isConclusion = isArgument && formulaIndex === rowResults.length - 1;
const fill = isConclusion
? (result ? '#ffcccc' : '#ffaaaa')
: (result ? '#ccf' : '#bbf');
svg += this.renderCell(x, y, this.cellWidth, this.cellHeight, fill);
svg += this.renderText(
x + this.cellWidth/2,
y + this.cellHeight/2,
this.formatter.formatBoolean(result),
16,
'bold'
);
});
// Validity cell
if (isArgument) {
const colIndex = variables.length + rowResults.length;
const x = this.padding + colIndex * this.cellWidth;
const allPremisesTrue = rowResults.slice(0, -1).every(result => result);
const conclusionFalse = !rowResults[rowResults.length - 1];
const isCounterexample = allPremisesTrue && conclusionFalse;
const fill = isCounterexample ? '#ffcccc' : '#ccffcc';
const text = this.formatter.formatValidity(!isCounterexample, 'svg');
const textColor = isCounterexample ? 'red' : 'green';
svg += this.renderCell(x, y, this.cellWidth, this.cellHeight, fill);
svg += this.renderText(
x + this.cellWidth/2,
y + this.cellHeight/2,
text,
20,
'bold',
textColor
);
}
});
return svg;
}
private renderFooter(numCols: number, numRows: number, isValid: boolean): string {
let svg = '';
const y = this.padding + this.headerHeight + numRows * this.cellHeight;
const fill = isValid ? '#e6ffe6' : '#ffe6e6';
const text = isValid ? 'VALID ARGUMENT' : 'INVALID ARGUMENT';
const textColor = isValid ? 'green' : 'red';
// Footer spans all columns except validity column
const footerWidth = (numCols - 1) * this.cellWidth;
svg += this.renderCell(this.padding, y, footerWidth, this.footerHeight, fill);
svg += this.renderText(
this.padding + footerWidth/2,
y + this.footerHeight/2,
text,
18,
'bold',
textColor
);
// Validity indicator
const validityX = this.padding + (numCols - 1) * this.cellWidth;
const validityText = this.formatter.formatValidity(isValid, 'svg');
svg += this.renderCell(validityX, y, this.cellWidth, this.footerHeight, fill);
svg += this.renderText(
validityX + this.cellWidth/2,
y + this.footerHeight/2,
validityText,
24,
'bold',
textColor
);
return svg;
}
private renderCell(x: number, y: number, width: number, height: number, fill: string): string {
return `<rect x="${x}" y="${y}" width="${width}" height="${height}"
fill="${fill}" stroke="#666" stroke-width="1" />`;
}
private renderText(
x: number,
y: number,
text: string,
fontSize: number,
fontWeight = 'normal',
fill = 'black'
): string {
return `<text x="${x}" y="${y}"
text-anchor="middle" dominant-baseline="middle"
font-family="Arial" font-size="${fontSize}" font-weight="${fontWeight}"
fill="${fill}">${text}</text>`;
}
}