Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ out
# Nuxt.js build / generate output
.nuxt
dist
docs/dist
.output

# Gatsby files
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Markup language for rendering network protocol diagrams.

## Interactive Demo

Try the live editor: [Demo](https://yeeshin504.github.io/protocol-ml/index.html) (after building with `npm run build:docs`)
Try the live editor [here](https://alieron.github.io/protocol-ml/)

### Development

Expand Down
2 changes: 2 additions & 0 deletions docs/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ const copySVGBtn = document.getElementById('copySVG');

const INITIAL_CODE = `def messageSpacing 20px
def participantSpacing 160px
def showTimeTicks true
def showGrid true

participant Alice a
participant Bob b
Expand Down
49 changes: 43 additions & 6 deletions src/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,33 @@ export interface AnnotationPos {
text: string;
}

export interface TickPos {
type: "tick";
y: number;
label: string;
}

export interface TimeAxisPos {
type: "timeAxis";
x: number;
y1: number;
y2: number;
}

export interface Diagram {
settings: Settings;
width: number;
height: number;
draws: (ParticipantPos | ArrowPos | AnnotationPos)[];
draws: (ParticipantPos | ArrowPos | AnnotationPos | TickPos | TimeAxisPos)[];
}

export function resolveLayout(entities: Entities): Diagram {
const { settings, participants, actions, numAnnotations } = entities;

// 1. check if need extra spacing for annotations

let timeTickMargin = settings.showTimeTicks ? 60 : 0;
let width = settings.participantSpacingX * (participants.length - 1)
+ 2 * settings.paddingX;
+ 2 * settings.paddingX + timeTickMargin;

if (numAnnotations > 0)
width += 2 * settings.annotationSpacingX;
Expand All @@ -51,9 +64,12 @@ export function resolveLayout(entities: Entities): Diagram {
// 3. resolve participant x position

const participantsX = new Map(); // map alias -> x coord
let timeAxisX = settings.paddingX + (numAnnotations > 0 ? settings.annotationSpacingX : 0) + (timeTickMargin > 0 ? 20 : 0);
{
// recomputing width, but whatever
let x = settings.paddingX + (numAnnotations > 0 ? settings.annotationSpacingX : 0);
let x = timeAxisX + timeTickMargin;
if (timeTickMargin === 0) {
x = settings.paddingX + (numAnnotations > 0 ? settings.annotationSpacingX : 0);
}
for (const participant of participants) {
participantsX.set(participant.alias, x);
x += settings.participantSpacingX;
Expand All @@ -63,7 +79,7 @@ export function resolveLayout(entities: Entities): Diagram {
// 4. resolve arrows and annotations

let counter = 0, counterMax = counter;
const draws: (ParticipantPos | ArrowPos | AnnotationPos)[] = [];
const draws: (ParticipantPos | ArrowPos | AnnotationPos | TickPos | TimeAxisPos)[] = [];

for (const action of actions) { // actions should be in order :D
switch (action.type) {
Expand All @@ -74,6 +90,8 @@ export function resolveLayout(entities: Entities): Diagram {

if (action.start) {
counter = action.start;
startY = counter;
endY = counter + 1;
}

if (action.end) {
Expand Down Expand Up @@ -134,6 +152,25 @@ export function resolveLayout(entities: Entities): Diagram {
};
});

if (settings.showTimeTicks || settings.showGrid) {
for (let i = 0; i <= counterMax; i++) {
draws.push({
type: "tick",
y: oldHeight + settings.messageSpacingY + i * settings.messageSpacingY,
label: `${i}`,
});
}
}

if (settings.showTimeTicks) {
draws.push({
type: "timeAxis",
x: timeAxisX,
y1: oldHeight,
y2: height,
});
}

height += settings.paddingY;

return {
Expand Down
17 changes: 14 additions & 3 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,17 @@ const DEFAULT_SETTINGS = {

paddingX: 30,
paddingY: 20,

showGrid: false,
showTimeTicks: false,
};

export type Settings = typeof DEFAULT_SETTINGS;
export type Settings = {
[key: string]: number | boolean | undefined;
} & typeof DEFAULT_SETTINGS;

export function parse(src: string): Entities {
const settings = DEFAULT_SETTINGS;
const settings: Settings = { ...DEFAULT_SETTINGS };

const participants: Participant[] = [];
const actions: (Arrow | Annotation)[] = [];
Expand All @@ -80,7 +85,13 @@ export function parse(src: string): Entities {

const [, name, value] = line.split(/\s+/);

settings[name] = parseFloat(value);
if (/^true$/i.test(value)) {
settings[name] = true;
} else if (/^false$/i.test(value)) {
settings[name] = false;
} else {
settings[name] = parseFloat(value);
}

continue;
}
Expand Down
51 changes: 50 additions & 1 deletion src/renderer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,43 @@
import { Entities, Settings } from "./parser";
import { AnnotationPos, ArrowPos, ParticipantPos, resolveLayout } from "./layout";
import { AnnotationPos, ArrowPos, ParticipantPos, TickPos, TimeAxisPos, resolveLayout } from "./layout";

function drawGrid(settings: Settings, ticks: TickPos[], width: number): string {
return ticks.map(tick =>
`<line stroke="#aaaaaa" stroke-dasharray="2" x1="0" y1="${tick.y}" x2="${width}" y2="${tick.y}" />`
).join("\n");
}

function drawTimeAxis(settings: Settings, axis: TimeAxisPos, ticks: TickPos[]): string {
const header = `
<text
x="${axis.x}"
y="${axis.y1 - settings.participantLabelHeight / 2}"
text-anchor="middle"
dominant-baseline="middle"
font-family="JetBrains Mono, monospace"
fill="white"
font-size="${settings.participantFontSize}"
>
Time
</text>
<line stroke="#aaaaaa" stroke-width="3" x1="${axis.x}" y1="${axis.y1}" x2="${axis.x}" y2="${axis.y2}" />`;

const tickElements = ticks.map(tick => `
<line stroke="#aaaaaa" x1="${axis.x - 5}" y1="${tick.y}" x2="${axis.x + 5}" y2="${tick.y}" />
<text
x="${axis.x - 10}"
y="${tick.y}"
text-anchor="end"
dominant-baseline="middle"
font-family="JetBrains Mono, monospace"
fill="white"
font-size="${settings.messageFontSize}"
>
${tick.label}
</text>`).join("\n");

return `${header}${tickElements}`;
}

function drawParticipant(settings: Settings, participant: ParticipantPos): string {
const { x, y1, y2, name } = participant;
Expand Down Expand Up @@ -202,6 +240,17 @@ export function renderSVG(entities: Entities): string {
`<rect width="100%" height="100%" fill="#333333"/>`
);

const ticks = draws.filter((d): d is TickPos => d.type === "tick");
const timeAxis = draws.find((d): d is TimeAxisPos => d.type === "timeAxis");

if (settings.showGrid) {
svg.push(drawGrid(settings, ticks, width));
}

if (settings.showTimeTicks && timeAxis) {
svg.push(drawTimeAxis(settings, timeAxis, ticks));
}

for (const draw of draws) {
switch (draw.type) {
case "participant":
Expand Down
Loading