Darstellung zweidimensionaler Daten

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
Kebap
User
Beiträge: 686
Registriert: Dienstag 15. November 2011, 14:20
Wohnort: Dortmund

Hallo Leute,

ich möchte mit Python eine Grafik erstellen, die Daten in Kästchen angeordnet darstellen kann, so ähnlich wie es derzeit in Excel per Hand geschieht. Nachfolgend ein zufällig ähnliches Beispiel. In dieser Art sollte Text und Farbe auf zweidimensionale Koordinaten eingetragen werden. Welche Python Module würden sich dort anbieten? Die Rohdaten liegen als csv auf einem Webserver, falls das irgendeine Relevanz hat.. (ich vermute nicht)

https://i.imgur.com/4bAb0zG.png
MorgenGrauen: 1 Welt, 8 Rassen, 13 Gilden, >250 Abenteuer, >5000 Waffen & Rüstungen,
>7000 NPC, >16000 Räume, >200 freiwillige Programmierer, nur Text, viel Spaß, seit 1992.
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Sollte zB mit Pillow gehen.
Benutzeravatar
noisefloor
User
Beiträge: 3843
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

für eine SVG-Grafik brauchst du nicht nicht mal ein Modul. Schnelles Beispiel mit 100x100px großen Quadraten:

CSV-Datei:

Code: Alles auswählen

"foo|green";"bar|red";"spam|blue"
"egg|blue";"hello|white";"world|purple"
"pyhton|green";"java|grey";"erlang|yellow"
Python-Code:

Code: Alles auswählen

import csv

svg = []
with open('square_input.csv') as f:
    csv_reader = csv.reader(f, delimiter=';', quotechar='"')
    for linecount, row in enumerate(csv_reader):
        if linecount==0:
            square_size = len(row)
            svg.append('<svg xmlns="http://www.w3.org/2000/svg" width="{0}" height="{0}">'.format(square_size*100))
        for item_count, item in enumerate(row):
            text, color = item.split('|')
            entry = '''<g>
<rect fill="{}" x="{}" y="{}" width="100" height="100"></rect>
<text x="{}" y="{}" fill="black" text-anchor="middle" alignment-baseline="central">{}</text>
</g>'''.format(color, item_count*100, linecount*100, item_count*100+50, linecount*100+50, text)
            svg.append(entry)
svg.append('</svg>')
with open('svg_square.svg', 'w') as out_file:
    out_file.write(''.join(svg))
Könnte man sicherlich noch übersichtlicher formatieren... ;-)

Gruß, noisefloor
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@noisefloor: wobei hier gilt: SVG ist XML und sollte auch so geschrieben werden, mit Hilfe von ElementTree. Bei entsprechenden Zeichen im Text fliegt Dir das sonst um die Ohren.

Code: Alles auswählen

import csv
import xml.etree.ElementTree as et

SVGNS = "{http://www.w3.org/2000/svg}"

def generate_rect(item, row, column):
    text, color = item.split('|')
    result = et.Element(SVGNS+'g')
    et.SubElement(result, SVGNS+'rect',
        y=str(row*100), x=str(column*100),
        width="100" height="100", fill=color)
    et.SubElement(result, SVGNS+'text',
        y=str(row*100 + 50), x=str(column*100 + 50),
        fill="black").text = text
    )
    return result

def generate_svg(rows):
    result = et.Element(SVGNS + svg)
    for row, items in enumerate(rows):
        square_size = len(row)
        for column, item in enumerate(items):
            result.append(generate_rect(item, row, column))
    result.attrib['width'] = str(square_size*100)
    result.attrib['height'] = str(square_size*100)
    return result

def main():
    with open('square_input.csv') as f:
        csv_reader = csv.reader(f, delimiter=';', quotechar='"')
        svg = generate_svg(csv_reader)
    with open('svg_square.svg', 'w') as out_file:
        doc = ElementTree(svg)
        doc.write(out_file)

if __name__ == '__main__':
    main()
Benutzeravatar
noisefloor
User
Beiträge: 3843
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
obei hier gilt: SVG ist XML und sollte auch so geschrieben werden, mit Hilfe von ElementTree
Stimmt, das ist mir dann später auch noch eingefallen, dass das mit Etree robuster ist.

Gruß, noisefloor
Benutzeravatar
Kebap
User
Beiträge: 686
Registriert: Dienstag 15. November 2011, 14:20
Wohnort: Dortmund

Danke für die eure raschen Antworten und beispielhaften Codes!

Pillow/PIL habe ich mir etwas angesehen, kenne ich bisher noch nicht. Dort fand ich aber hauptsächlich Funktionen, um Bilder zu öffnen, bearbeiten, usw. Weniger um sie aus strukturierten Daten selbst zu erstellen. Habe auch keine Beispiele gefunden, die so etwas damit tun. Hingegen die Codes aus dem Tutorial waren gar nicht in sich lauffähig, mehr Schnipsel. Da hatte ich noch keine Zeit mich genauer einzuwühlen, bis die zweite Antwort kam.

SVG finde ich eine sehr spannende Idee! Ich wusste gar nicht, dass das einfache Textdateien sind. Super! Für die Umwandlung von SVG nach PNG könnte ich dann ja doch noch mal auf PIL zurückgreifen, oder gibt's da lohnendere Wege?

Der Beispielcode von SIrius konnte bei mir nicht ausgeführt werden. Ich muss da noch genauer die Dokumentation lesen und verstehen, bis ich die gewünschte Logik selbst reparieren kann. Manches habe ich repariert bekommen, aber eben nicht alles:

Zeile 11: Komma fehlt, ok.
Zeile 14: Klammer unerwartet. Schon kniffliger, da ich ElementTree auch noch nicht kenne. Vermutlich aber analog Zeile 11 gemeint, eben mit text statt fill. Wobei dann lustigerweise nur text=text stehen bleibt, aber das hat wohl mit Scoping zu tun)
Zeile 19: svg ist unbekannt. Vermutlich ist hier "svg" gemeint?
Zeile 21: len(row) ist nicht definiert, row ist nur eine Zahl. Ist len(rows) gemeint? Macht aber mehr Sinn außerhalb der Schleife..? Oder geht es sinnvollerweise eher um len(items)? Eine andere Interpretation habe ich nicht gefunden, muss die Formeln genauer prüfen, wo das dann angewandt wird.
Zeile 33: Hier fehlt et. vor ElementTree

Letztendlich wird eine Datei erzeugt, aber noch nicht der Text ins Feld geschrieben.

Ansonsten wie gesagt schon ein sehr spannender Ansatz. Ich werde mal die 100 als magische Zahl in eine globale Konstante herausziehen, und mich weiter in SVG und ElementTree einlesen, welche Möglichkeiten sich noch bieten. Beispielsweise überlappt sich bestimmt Text aus mehreren Kästchen, wenn die kleiner dimensioniert sind, das sollte ggf. unterdrückt werden. Ob dann letztendlich überhaupt PNG benötigt wird, muss ich noch mal abklären.
MorgenGrauen: 1 Welt, 8 Rassen, 13 Gilden, >250 Abenteuer, >5000 Waffen & Rüstungen,
>7000 NPC, >16000 Räume, >200 freiwillige Programmierer, nur Text, viel Spaß, seit 1992.
Benutzeravatar
Kebap
User
Beiträge: 686
Registriert: Dienstag 15. November 2011, 14:20
Wohnort: Dortmund

Kebap hat geschrieben: Zeile 14: Klammer unerwartet. Schon kniffliger, da ich ElementTree auch noch nicht kenne. Vermutlich aber analog Zeile 11 gemeint, eben mit text statt fill. Wobei dann lustigerweise nur text=text stehen bleibt, aber das hat wohl mit Scoping zu tun)
Alles Quatsch, einfach Klammer aus Zeile 15 weg, dann passt's schon so, und der Text wird auch ins Feld geschrieben.

Insgesamt lande ich dann hier:

Code: Alles auswählen

    import csv
    import xml.etree.ElementTree as et

    SVGNS = "{http://www.w3.org/2000/svg}"
    SIZE = 100

    def generate_rect(item, row, column):
        text, color = item.split('|')
        result = et.Element(SVGNS+'g')
        et.SubElement(result, SVGNS+'rect',
            y=str(row*SIZE), x=str(column*SIZE),
            width=str(SIZE), height=str(SIZE), fill=color)
        et.SubElement(result, SVGNS+'text',
            y=str(row*SIZE + SIZE/2), x=str(column*SIZE + SIZE/2),
            fill='black').text = text
        return result

    def generate_svg(rows):
        result = et.Element(SVGNS + 'svg')
        for row, items in enumerate(rows):
            square_size = len(items)
            for column, item in enumerate(items):
                result.append(generate_rect(item, row, column))
        result.attrib['width'] = str(square_size*SIZE)
        result.attrib['height'] = str(square_size*SIZE)
        return result

    def main():
        filename_in = 'square_input.csv'
        filename_out = 'svg_square.svg'
        with open(filename_in) as f:
            csv_reader = csv.reader(f, delimiter=';', quotechar='"')
            svg = generate_svg(csv_reader)
        with open(filename_out, 'w') as out_file:
            doc = et.ElementTree(svg)
            doc.write(out_file)

    if __name__ == '__main__':
        main()
Dazu noch eine kurze Frage:
noisefloor hatte die folgenden Parameter angegeben, um den Text zu zentrieren: text-anchor='middle' und alignment-baseline="central"
Wenn ich diese bei SubElement übergeben will, wird das Minuszeichen als Rechenoperation missverstanden. Was tun?
MorgenGrauen: 1 Welt, 8 Rassen, 13 Gilden, >250 Abenteuer, >5000 Waffen & Rüstungen,
>7000 NPC, >16000 Räume, >200 freiwillige Programmierer, nur Text, viel Spaß, seit 1992.
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@Kebap: über Keyword-Argumente kann man nur Attribute setzen, die gültigen Bezeichnern entsprechen. Andere muß man per Dictionary-Zuweisung machen. Oder gleich das `style`-Attribut benutzen, das ist sowieso besser.
Benutzeravatar
noisefloor
User
Beiträge: 3843
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

das `text-anchor="middle" alignment-baseline="central"` habe ich übrigens aus einem Thread bei SO gefunden, als ich nach Zentrierung von Text in SVG-Grafiken gesucht habe. Sind aber beides gültige Attribute lt. MDN Doku.

Gruß, noisefloor
Benutzeravatar
noisefloor
User
Beiträge: 3843
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

das geht mit PIL (bzw. Pillow) auch ganz einfach, ein Minibeispiel mit nur einem Quadrat:

Code: Alles auswählen

from PIL import Image, ImageFont, ImageDraw

font = ImageFont.truetype('FreeMono.ttf', 14, encoding='unic')
img = Image.new('RGBA', (200, 200), 'white')
draw = ImageDraw.Draw(img)
draw.rectangle(((0, 0), (100, 100)), fill='yellow')
text = 'python'
text_size = font.getsize(text)
draw.text(((100-text_size[0])/2, (100-text_size[1])/2), text,
          font=font, fill='black')
img.save('square.png', 'PNG')
Gruß, noisefloor
Benutzeravatar
Kebap
User
Beiträge: 686
Registriert: Dienstag 15. November 2011, 14:20
Wohnort: Dortmund

noisefloor hat geschrieben:

Code: Alles auswählen

draw = ImageDraw.Draw(img)
Das ist schon fast Poesie... :mrgreen:

Danke, genau so was hat gefehlt. Werde ich auch mal ausprobieren!
MorgenGrauen: 1 Welt, 8 Rassen, 13 Gilden, >250 Abenteuer, >5000 Waffen & Rüstungen,
>7000 NPC, >16000 Räume, >200 freiwillige Programmierer, nur Text, viel Spaß, seit 1992.
Antworten