Aktenordner in TikZ

Hier ein Beispiel, wie man mit TikZ einen Aktenordner in TikZ zeichnen kann.

\documentclass[tikz,border=5pt]{standalone}
\begin{document}
\begin{tikzpicture}[line join=round, line cap=round]
 
% ------------------------------
% Parameter
% ------------------------------
\def\ordnerwidth{2.5}   % Rückenbreite des Ordners
\def\ordnerheight{11}   % Höhe des Ordners
 
% Abgeleitete Maße
\pgfmathsetmacro{\lochdiam}{0.7*\ordnerwidth}
\pgfmathsetmacro{\labelwidth}{0.8*\ordnerwidth}
\pgfmathsetmacro{\lochx}{0.5*\ordnerwidth}
\pgfmathsetmacro{\labelx}{0.5*(\ordnerwidth-\labelwidth)}
 
% ------------------------------
% Zeichnung
% ------------------------------
 
% Rücken
\draw[fill=gray!20] (0,0) rectangle (\ordnerwidth,\ordnerheight);
 
% Etikettfeld (mittig auf Rücken)
\draw[fill=white] (\labelx,3) rectangle ++(\labelwidth,7);
\foreach \y in {8.5,8,7.5,7,6.5} {
  \draw (\labelx+0.2,\y) -- (\labelx+\labelwidth-0.2,\y);
}
 
% Griffloch (mittig unten am Rücken)
\draw[fill=black!40] (\lochx,1.2) circle (\lochdiam/2);
\draw[fill=white] (\lochx,1.2) circle (\lochdiam/2.2);
 
\end{tikzpicture}
\end{document}

Und hier der Code für den Ordner in 3D:

\documentclass[tikz,border=5pt]{standalone}
\begin{document}
\begin{tikzpicture}[line join=round, line cap=round]
 
% ------------------------------
% Parameter
% ------------------------------
\def\ordnerwidth{2.5}   % Rückenbreite des Ordners
\def\ordnerheight{10}   % Höhe
\def\depth{2}           % Tiefe in die Perspektive
 
% Abgeleitete Maße
\pgfmathsetmacro{\lochdiam}{0.8*\ordnerwidth}
\pgfmathsetmacro{\labelwidth}{0.8*\ordnerwidth}
\pgfmathsetmacro{\lochx}{0.5*\ordnerwidth}
\pgfmathsetmacro{\labelx}{0.5*(\ordnerwidth-\labelwidth)}
 
% ------------------------------
% Zeichnung
% ------------------------------
 
% Rückseite (Rücken sichtbar)
\draw[fill=gray!20] (0,0) -- (\ordnerwidth,0) -- (\ordnerwidth,\ordnerheight) -- (0,\ordnerheight) -- cycle;
 
% Seitenfläche
\draw[fill=gray!10] (\ordnerwidth,0) -- (\ordnerwidth+\depth,0.6) -- (\ordnerwidth+\depth,\ordnerheight+0.6) -- (\ordnerwidth,\ordnerheight) -- cycle;
 
% Oberseite
\draw[fill=gray!30] (0,\ordnerheight) -- (\ordnerwidth,\ordnerheight) -- (\ordnerwidth+\depth,\ordnerheight+0.6) -- (\depth,\ordnerheight+0.6) -- cycle;
 
% Etikettfeld (mittig auf Rücken)
\draw[fill=white] (\labelx,6) rectangle ++(\labelwidth,3);
\foreach \y in {8.5,8,7.5,7,6.5} {
  \draw (\labelx+0.2,\y) -- (\labelx+\labelwidth-0.2,\y);
}
 
% Griffloch (mittig unten am Rücken)
\shade[ball color=black!50] (\lochx,1.2) circle (\lochdiam/2);
\fill[white] (\lochx,1.2) circle (\lochdiam/2.5);
 
% Umrandung
\draw[thick] (0,0) -- (\ordnerwidth,0) -- (\ordnerwidth+\depth,0.6) -- (\ordnerwidth+\depth,\ordnerheight+0.6) -- (\depth,\ordnerheight+0.6) -- (0,\ordnerheight) -- cycle;
 
\end{tikzpicture}
\end{document}

Behringer Abacus / Mutuable Instruments Maths in TikZ

Hier ein Versuch, ein Maths bzw. Abacus Eurorack Modul mit TikZ zu zeichnen.

%!TEX TS-program = pdflatex
\documentclass[12pt,ngerman,tikz]{standalone}
 
\begin{document}
\begin{tikzpicture}[red,draw=red, fill=red]
 
% a plug
\newcommand{\plug}[2]{% three circles, inner one is filled
    \fill (#1,#2) circle (0.1875cm);
    \draw (#1,#2) circle (0.28cm);
    \draw (#1,#2) circle (0.42cm);
}
 
% buttons
\newcommand{\button}[2]{% three circles, inner one is filled
    \fill (#1,#2) circle (0.5cm);
    \fill[white] (#1,#2) circle (0.4cm);
}
 
% a plug with a label above
\newcommand{\labelplug}[3]{%
    \fill (#1,#2) circle (0.1875cm);
    \draw (#1,#2) circle (0.28cm);
    \draw (#1,#2) circle (0.42cm);
    \node[above=0.35cm,font=\sffamily\bfseries\scriptsize] at (#1,+#2) {#3};
}
 
%  \knob{x}{y}{size}{angle} 
\newcommand{\knob}[4]{%
  \pgfmathsetmacro{\radius}{#3}%
  \pgfmathsetmacro{\reduced}{\radius * 0.8}%
  \pgfmathsetmacro{\tikzAngle}{90 - #4}%
  \draw[thick, fill=white!30] (#1,#2) circle (\radius cm);
  \draw[line width=2pt, red] (#1,#2) -- ++(\tikzAngle:\reduced cm);
}
 
% plug with label and led, in a frame
\newcommand{\labelplugled}[3]{%
    \fill (#1,#2) circle (0.1875cm);
    \draw (#1,#2) circle (0.28cm);
    \draw (#1,#2) circle (0.42cm);
    \node[above=0.35cm,font=\sffamily\bfseries\scriptsize] at (#1,#2) {#3};
     \draw[rounded corners=2pt]
      (#1-0.5, #2-0.5)
        rectangle
      (#1+0.5, {#2+1.2});
    \draw (#1,#2+0.945) circle (0.1cm);
}
 
% plug with label but no led, in a frame
\newcommand{\labelplugnoled}[3]{%
    \fill (#1,#2) circle (0.1875cm);
    \draw (#1,#2) circle (0.28cm);
    \draw (#1,#2) circle (0.42cm);
    \node[above=0.35cm,font=\sffamily\bfseries\scriptsize] at (#1,#2) {#3};
     \draw[rounded corners=2pt]
      (#1-0.5, #2-0.5)
        rectangle
      (#1+0.5, {#2+1.2});
 
}
 
% Screenshot from the manual, in the background
%\node[anchor=north west ,inner sep=0] (frame1) at (1,13)    {\includegraphics[width=10cm]{2025-09-20 04 09 06.png}};
 
% grid
\draw[help lines,red] (0,0) grid (13,14);
 
\draw[very thick, rounded corners=2pt](1,0.35) rectangle (11,13);
 
\labelplug{1.75}{11.5}{IN 1};
\labelplug{2.85}{11.5}{TRIG 1};
 
\labelplug{5.45}{11.5}{IN 2};
\labelplug{6.55}{11.5}{IN 3};
 
\labelplug{9.175}{11.5}{TRIG 4};
\labelplug{10.25}{11.5}{IN 4};
 
\plug{1.75}{10.2};
\plug{1.75}{9};
\plug{1.75}{7.9};
\plug{1.75}{6.55};
 
\plug{10.25}{10.2};
\plug{10.25}{9};
\plug{10.25}{7.9};
\plug{10.25}{6.55};
 
 
\labelplugled{1.75}{1.8}{EOR};
\labelplugled{2.9}{1.8}{$\smallint$};
 
\labelplugnoled{4.9}{1.8}{OR};
\labelplugled{6}{1.8}{SUM};
\labelplugnoled{7.1}{1.8}{INV};
 
\labelplugled{9.14}{1.8}{$\smallint$};
\labelplugled{10.25}{1.8}{EOC};
 
\labelplugnoled{4.25}{3.6}{OUT 1};
\labelplugnoled{5.4}{3.6}{OUT 2};
\labelplugnoled{6.55}{3.6}{OUT 3};
\labelplugnoled{7.7}{3.6}{OUT 4};
 
\knob{3.5}{10.2}{0.7}{0}
\knob{8.5}{10.2}{0.7}{0}
 
\knob{3.5}{7.9}{0.7}{0}
\knob{8.5}{7.9}{0.7}{0}
 
\knob{3.5}{5.7}{0.7}{0}
\knob{8.5}{5.7}{0.7}{0}
 
\knob{5}{9}{0.35}{0}
\knob{7}{9}{0.35}{0}
\knob{5}{6.75}{0.35}{0}
\knob{7}{6.75}{0.35}{0}
 
\button{2.3}{4.15}
\button{9.7}{4.15}
 
\node at (5,9.6){\sffamily\bfseries\footnotesize1};
\node at (7,9.6){\sffamily\bfseries\footnotesize4};
\node at (5,7.35){\sffamily\bfseries\footnotesize2};
\node at (7,7.35){\sffamily\bfseries\footnotesize4};
 
\node at (4.75,8.5){\sffamily\bfseries--};
\node at (5.3,8.5){\sffamily\bfseries+};
\node at (4.75,6.25){\sffamily\bfseries--};
\node at (5.3,6.25){\sffamily\bfseries+};
 
\node at (6.75,8.5){\sffamily\bfseries--};
\node at (7.3,8.5){\sffamily\bfseries+};
\node at (6.75,6.25){\sffamily\bfseries--};
\node at (7.3,6.25){\sffamily\bfseries+};
 
 
\draw[very thick] (3.4,11.5) -- (3.95,11.5) -- (5,10.5) -- (5,9.8);
\draw[very thick] (8.7,11.5) -- (8.1,11.5) -- (7,10.5) -- (7,9.8);
 
\draw[very thick] (5.45,10.85) -- (5.7,10.6) -- (5.7,7.5) -- (5.3,7.1);
\draw[very thick] (6.55,10.85) -- (6.3,10.6) -- (6.3,7.5) -- (6.7,7.1);
 
\foreach \x in {0,1,2,3,4,5,6,7,8,9,10,11,12} {
\node(a) at (\x,-0.5){\x};}
 
\foreach \y in {0,1,2,3,4,5,6,7,8,9,10,11,12,13} {
\node(a) at (0.5,\y){\y};}
 
\end{tikzpicture}
\end{document}

abacus_tikz

ISO 3864, 7010 und 7001 Zeichen mit LaTeX setzen

Mit dem isosigns Paket kann man in LaTeX Zeichen aus den ISO Standards 3864, 7010 und 7001 setzen.

Link zur Doku: https://ctan.org/pkg/isosigns

Hier ein Screenshot aus der Dokumentation:

Neue Excel Office365 Funktionen

Excel 365 aus Office365 hat eine Reihe neuer Funktionen, die ich auch noch nicht kannte. Hier eine Liste, die mir ChatGPT ausgespuckt hat.

Name (Englisch) Erläuterung
TEXTKETTE (TEXTJOIN) Verbindet Text aus mehreren Zellen mit einem Trennzeichen. Unterstützt auch Zellbereiche.
TEXTVERKETTEN (CONCAT) Ähnlich wie VERKETTEN, aber flexibler und moderner.
WENNS (IFS) Ersetzt verschachtelte WENN-Funktionen durch eine klarere Syntax.
MAXWENNS (MAXIFS) Gibt den größten Wert zurück, der bestimmte Kriterien erfüllt.
MINWENNS (MINIFS) Gibt den kleinsten Wert zurück, der bestimmte Kriterien erfüllt.
ERSTERWERT (SWITCH) Gibt einen Wert basierend auf dem ersten zutreffenden Vergleich zurück.
FILTER Filtert Daten dynamisch basierend auf Bedingungen.
SORTIEREN (SORT) Sortiert Daten dynamisch.
SORTIERENNACH (SORTBY) Sortiert Daten basierend auf einem anderen Bereich.
EINDEUTIG (UNIQUE) Gibt eindeutige Werte aus einem Bereich zurück.
SEQUENZ (SEQUENCE) Erstellt eine Liste von Zahlen in einer Sequenz.
WAHL.ZEILE (XLOOKUP) Moderner Ersatz für SVERWEIS und WVERWEIS.
WAHL.BEREICH (XMATCH) Gibt die Position eines Werts in einem Bereich zurück.
LET Ermöglicht das Definieren von Variablen innerhalb einer Formel.
LAMBDA Erstellt benutzerdefinierte Funktionen direkt in Excel.
TEXTSPLIT Teilt Text anhand eines Trennzeichens in mehrere Zellen auf.
TEXTBEFORE / TEXTAFTER Gibt Text vor oder nach einem bestimmten Zeichen zurück.
VSTACK / HSTACK Stapelt Zellbereiche vertikal oder horizontal.
WRAPROWS / WRAPCOLS Wandelt eine Liste in ein Array mit mehreren Zeilen oder Spalten um.
TAKE / DROP Gibt die ersten/letzten Elemente eines Bereichs zurück oder entfernt sie.
EXPAND Erweitert einen Bereich auf eine bestimmte Größe.
TOCOL / TOROW Wandelt einen Bereich in eine Spalte oder Zeile um.

Outer Apply und Cross Apply in SQL nutzen – NULL-Werte auffüllen

This entry is part 3 of 3 in the series Outer Apply und Cross Apply

Schauen wir uns das Forward Filling an. Je nach Version des SQL Servers funktioniert eine Version oder leider nicht. 🙂

Mit SQL Server 2022 kann man den folgenden Code nutzen, um einen weiteren CTE zu bauen:

Filled AS (
    SELECT
        ContNo,
        MonthEnd,
        Amount,
        last_value(Amount) IGNORE NULLS
        OVER (
            PARTITION BY ContNo
            ORDER BY MonthEnd
            ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
        ) AS ForwardFilledAmount
    FROM Combined
)
SELECT * FROM Filled

Versionen von SQL Server vor 2022 unterstützen leider „ignore nulls“ nicht, hier kann man dann folgenden Code nutzen:

Filled AS (
    SELECT
        c.ContNo,
        c.MonthEnd,
        c.Amount,
        ca.ForwardFilledAmount
    FROM Combined c
    OUTER APPLY (
        SELECT TOP 1 Amount AS ForwardFilledAmount
        FROM Combined c2
        WHERE c2.ContNo = c.ContNo
          AND c2.MonthEnd <= c.MonthEnd
          AND c2.Amount IS NOT NULL
        ORDER BY c2.MonthEnd DESC
    ) ca
)
SELECT * FROM Filled

OUTER APPLY funktioniert dabei so ähnlich wie ein LEFT JOIN. Der Unterschied besteht darin, dass beim OUTER APPLY die rechte Seite von der linken Seite abhängt. In einem weiteren Artikel zum Thema werde ich noch ein paar Beispiele dazu zeigen. CROSS APPLY ist ähnlich, hier ist es aber kein Äquivalent zum LEFT JOIN, sondern zum INNER JOIN. Auch dazu mehr in einem der weiteren Artikel.

Outer Apply und Cross Apply in SQL nutzen – Die Datenmenge bauen

This entry is part 2 of 3 in the series Outer Apply und Cross Apply

Wir kennen jetzt das Problem, nun können wir uns an die Lösung wagen! In diesem Blogbeitrag werden wir alles vorbereiten, was wir für das anschließende „Forward Filling“ der Daten brauchen.

Im ersten Schritt bestimmen wir für die einzelnen Verträge die Monatsendwerte von Start des jeweiligen Vertrags bis zu dessen Ende. Dazu nutze ich den den folgenden rekursiven CTE (Common Table Expression)

WITH MonthSequence AS (
    SELECT
        c.ContNo,
        EOMONTH(c.StartDate) AS MonthEnd,
        c.EndDate
    FROM Contracts c
    UNION ALL
    SELECT
        ms.ContNo,
        EOMONTH(DATEADD(MONTH, 1, ms.MonthEnd)),
        ms.EndDate
    FROM MonthSequence ms
    WHERE EOMONTH(DATEADD(MONTH, 1, ms.MonthEnd)) <= ms.EndDate
)

Das erzeugt uns die folgende Sequenz:

ContNo MonthEnd EndDate
123 2025-01-31 2025-12-31
123 2025-02-28 2025-12-31
123 2025-03-31 2025-12-31
123 2025-04-30 2025-12-31
123 2025-05-31 2025-12-31
123 2025-06-30 2025-12-31
123 2025-07-31 2025-12-31
123 2025-08-31 2025-12-31
123 2025-09-30 2025-12-31
123 2025-10-31 2025-12-31
123 2025-11-30 2025-12-31
123 2025-12-31 2025-12-31
456 2024-01-31 2024-12-31
456 2024-02-29 2024-12-31
456 2024-03-31 2024-12-31
456 2024-04-30 2024-12-31
456 2024-05-31 2024-12-31
456 2024-06-30 2024-12-31
456 2024-07-31 2024-12-31
456 2024-08-31 2024-12-31
456 2024-09-30 2024-12-31
456 2024-10-31 2024-12-31
456 2024-11-30 2024-12-31
456 2024-12-31 2024-12-31

Als nächstes suchen wir uns die End-of-Month der Cashflows raus, die wir im nächsten Schritt mit den End-of-Month aus Schritt 1 kombinieren werden.

WITH CashflowBuckets AS (
    SELECT
        ContNo,
        EOMONTH(CashflowDate) AS MonthEnd,
        Amount
    FROM Cashflows
)
SELECT * FROM CashflowBuckets
ContNo MonthEnd Amount
123 2025-01-31 100.00
123 2025-04-30 110.00
123 2025-07-31 105.00
123 2025-12-31 120.00
456 2024-01-31 100.00
456 2024-06-30 130.00
456 2024-12-31 101.00

Die Kombination ist dann recht einfach und gibt uns die Liste aller End-of-Months aus mit den dazugehörigen Cashflows.

WITH MonthSequence AS (
    SELECT
        c.ContNo,
        EOMONTH(c.StartDate) AS MonthEnd,
        c.EndDate
    FROM Contracts c
    UNION ALL
    SELECT
        ms.ContNo,
        EOMONTH(DATEADD(MONTH, 1, ms.MonthEnd)),
        ms.EndDate
    FROM MonthSequence ms
    WHERE EOMONTH(DATEADD(MONTH, 1, ms.MonthEnd)) <= ms.EndDate
)
,CashflowBuckets AS (
    SELECT
        ContNo,
        EOMONTH(CashflowDate) AS MonthEnd,
        Amount
    FROM Cashflows
)
,Combined AS (
   SELECT
        ms.ContNo,
        ms.MonthEnd,
        cb.Amount
    FROM MonthSequence ms
    LEFT JOIN CashflowBuckets cb
        ON ms.ContNo = cb.ContNo
        AND ms.MonthEnd = cb.MonthEnd
)
 
SELECT * FROM Combined

In gelb sind die Einträge markiert, die wir im finalen Schritt mit den jeweils letzten gültigen Cashflow-Werten befüllen müssen.

ContNo MonthEnd Amount
123 2025-01-31 100.00
456 2024-01-31 100.00
456 2024-02-29 NULL
456 2024-03-31 NULL
456 2024-04-30 NULL
456 2024-05-31 NULL
456 2024-06-30 130.00
456 2024-07-31 NULL
456 2024-08-31 NULL
456 2024-09-30 NULL
456 2024-10-31 NULL
456 2024-11-30 NULL
456 2024-12-31 101.00
123 2025-02-28 NULL
123 2025-03-31 NULL
123 2025-04-30 110.00
123 2025-05-31 NULL
123 2025-06-30 NULL
123 2025-07-31 105.00
123 2025-08-31 NULL
123 2025-09-30 NULL
123 2025-10-31 NULL
123 2025-11-30 NULL
123 2025-12-31 120.00

Outer Apply und Cross Apply in SQL nutzen – Einleitung

This entry is part 1 of 3 in the series Outer Apply und Cross Apply

Ich halte mich schon recht erfahren im Umgang SQL, kürzlich bin ich aber an einer Ecke von SQL vorbeigekommen, die ich auch noch nicht kannte. Dabei handelt es sind um die „OUTER APPLY“ bzw. „CROSS APPLY“ Operatoren.

Um die Lösung herzuleiten betrachten wir zuerst das Problem:

Gegeben seien Kreditverträge mit verschiedenen Zahlplänen. Ein Vertrag zahlt vielleicht monatlich, einer zahlt quartalsweise. Ein Vertrag hat dabei ein Startdatum und ein Enddatum sowie verschiedene Cashflows.

Hier ein paar Testdaten mit den entsprechenden Tabellen, die dazugehörigen SQL-Statements folgen später.

Cashflows

ContNo StartDate EndDate
123 2025-01-01 2025-12-31
456 2024-01-01 2024-12-31

Cashflows

ContNo CashflowDate Amount
123 2025-01-05 100.0
123 2025-04-07 110.0
123 2025-07-06 105.0
123 2025-12-16 120.0
456 2024-01-05 100.0
456 2024-06-12 130.0
456 2025-12-22 101.0

Das Problem ist jetzt, wie kann man eine monatliche Übersicht pro Vertrag bekommen, bei der in den Monaten, wo es kein Cashflow gab, einfach der letzte Cashflow angezeigt wird? Das Endergebnis soll wie folgt aussehen:

ContNo CashflowDate Amount
123 2025-01-31 100,00
123 2025-02-28 100,00
123 2025-03-31 100,00
123 2025-04-30 110,00
123 2025-05-31 110,00
123 2025-06-30 110,00
123 2025-07-31 105,00
123 2025-08-31 105,00
123 2025-09-30 105,00
123 2025-10-31 105,00
123 2025-11-30 105,00
123 2025-12-31 120,00
456 2024-01-31 100,00
456 2024-02-29 100,00
456 2024-03-31 100,00
456 2024-04-30 100,00
456 2024-05-31 100,00
456 2024-06-30 130,00
456 2024-07-31 130,00
456 2024-08-31 130,00
456 2024-09-30 130,00
456 2024-10-31 130,00
456 2024-11-30 130,00
456 2024-12-31 101,00

Im nächsten Teil fangen wir dann mit den SQL Statements an.

Zahlungspläne mit Python basteln

Für ein kleines Projekt brauchte ich die Möglichkeit, flexible Zahlungspläne zu erzeugen. Der folgende Python-Code tut genau das und erzeugt auch gleich passende INSERT Statements für Postgres.

Das CREATE TABLE ist wie folgt:

CREATE TABLE paymentplan (
    contract INTEGER NOT NULL,
    paymentdate INTEGER NOT NULL,
    paymenttype INTEGER NOT NULL,
    amount NUMERIC(12, 2) NOT NULL
);
import pandas as pd
from datetime import datetime
from dateutil.relativedelta import relativedelta
 
def genPaymentPlan(contract, principal, paymentsperyear, interestrate, startdate, years):
    interest = interestrate/100*principal/paymentsperyear
    date = datetime.strptime(startdate, '%Y%m%d').date()
    output = pd.DataFrame(columns=['Contract', 'Paymentdate', 'Paymenttype', 'Amount'])
 
    # Add a row to the DataFrame
    start = {'Contract': contract, 'Paymentdate': startdate, 'Paymenttype': 1, 'Amount' : principal}
    output = pd.concat([output, pd.DataFrame([start])], ignore_index=True)  
 
    for payment in range(paymentsperyear*years):
        new_date = (date + relativedelta(months=12/paymentsperyear))
        date = new_date
        interest_line = {'Contract': contract, 'Paymentdate': new_date.strftime('%Y%m%d'),'Paymenttype': 2, 'Amount' : -1*interest}
        output = pd.concat([output, pd.DataFrame([interest_line])], ignore_index=True)  
 
    end = {'Contract': contract, 'Paymentdate': date.strftime('%Y%m%d'), 'Paymenttype': 3, 'Amount' : -principal}
    output = pd.concat([output, pd.DataFrame([end])], ignore_index=True)          
    return output
 
 
test = genPaymentPlan(123458, 2000, 2, 12, '20260101', 4)
#print(test)
 
 
 
# Generate INSERT statements
print("Generated SQL INSERT statements:\n")
 
for index, row in test.iterrows():
    contract = int(row['Contract'])
    date = row['Paymentdate']  # already string in format YYYY-MM-DD
    type =  row['Paymenttype'] 
    amount = float(row['Amount'])
 
 
    insert = f"INSERT INTO paymentplan (contract, paymentdate,paymenttype, amount) VALUES ({contract}, '{date}', {type} , {amount});"
    print(insert)

Drumpatterns mit TikZ zeichnen

Hier ein wenig TikZ-Code, um Drumpatterns für einen Drumcomputer mit TikZ zu zeichnen. Das Beispiel ist für eine Spur, es lassen sich aber beliebig viele Spuren untereinandersetzen.

%!TEX TS-program = Arara
% arara: lualatex: {shell: no}
\documentclass{scrartcl}
\usepackage[margin=1cm]{geometry}
\usepackage{tikz}
 
\begin{document}
 
\begin{tikzpicture}
    % Define the pattern as a comma-separated list
    \def\pattern{x,-,x,x,-,x,x,-,-,-,x,x,-,-,-,x}
    \def\cellsizex{1cm}
    \def\cellsizey{0.5cm}
 
    %\draw (0,0) rectangle ++ (\cellsizex-1, \cellsizey);
    \node[draw, rectangle,minimum width = \cellsizex, align=left,minimum height=\cellsizey] (a) at (\cellsizex/2,\cellsizey/2){1}; 
 
    % Loop through each character in the pattern
    \foreach \cell [count=\index from 1] in \pattern {
        % If 'x' fill the cell, otherwise leave it empty
        \if\cell x
            \fill[gray,draw=black] ({\index*\cellsizex}, 0) rectangle ++(\cellsizex, \cellsizey);
        \else
            \draw ({\index*\cellsizex}, 0) rectangle ++(\cellsizex, \cellsizey);
        \fi
    }
\end{tikzpicture}
 
\end{document}

drumbrute_pattern2.pdf

Alle PDFs in einem Verzeichnis nach PNG verwandeln

Hier ein kurzes Windows Batch-Skript, das alle PDFs mit dem Namensschema pg_* in PNGs umwandelt. pdftoppm ist Teil von poppler und muss installiert sein.

Ich nutze das, um PDF-Präsentationen — die ich vorher mit pdftk dateiname.pdf burst in einzelne PDFs zerlegt hatte — nach PNG zu wandeln.

@echo off
setlocal enabledelayedexpansion

REM Set the output image format (e.g., png, jpeg, or ppm)
set FORMAT=png

REM Loop through all PDF files in the current directory
for %%F in (pg*.pdf) do (
    echo Converting %%F...
    pdftoppm -%FORMAT% "%%F" "%%~nF"
)

echo Done!
pause