Buat xkcd-Style Narrative Charts

45

Dalam salah satu strip xkcd yang lebih ikonik, Randall Munroe memvisualisasikan garis waktu dari beberapa film dalam grafik naratif:

masukkan deskripsi gambar di sini (Klik untuk versi yang lebih besar.)

Sumber: xkcd No. 657 .

Diberikan spesifikasi garis waktu film (atau narasi lain), Anda harus membuat bagan tersebut. Ini adalah kontes popularitas, jadi jawaban dengan suara terbanyak (bersih) akan menang.

Persyaratan minimum

Untuk sedikit memperketat spesifikasi, berikut adalah serangkaian fitur minimum yang harus diterapkan setiap jawaban:

  • Ambil sebagai masukan daftar nama karakter, diikuti oleh daftar acara. Setiap peristiwa adalah daftar karakter yang sekarat, atau daftar kelompok karakter (menandakan karakter mana yang saat ini bersama-sama). Berikut adalah satu contoh bagaimana narasi Jurassic Park dapat dikodekan:

    ["T-Rex", "Raptor", "Raptor", "Raptor", "Malcolm", "Grant", "Sattler", "Gennaro",
     "Hammond", "Kids", "Muldoon", "Arnold", "Nedry", "Dilophosaurus"]
    [
      [[0],[1,2,3],[4],[5,6],[7,8,10,11,12],[9],[13]],
      [[0],[1,2,3],[4,7,5,6,8,9,10,11,12],[13]],
      [[0],[1,2,3],[4,7,5,6,8,9,10],[11,12],[13]],
      [[0],[1,2,3],[4,7,5,6,9],[8,10,11,12],[13]],
      [[0,4,7],[1,2,3],[5,9],[6,8,10,11],[12],[13]],
      [7],
      [[5,9],[0],[4,6,10],[1,2,3],[8,11],[12,13]],
      [12],
      [[0, 5, 9], [1, 2, 3], [4, 6, 10, 8, 11], [13]], 
      [[0], [5, 9], [1, 2], [3, 11], [4, 6, 10, 8], [13]], 
      [11], 
      [[0], [5, 9], [1, 2, 10], [3, 6], [4, 8], [13]], 
      [10], 
      [[0], [1, 2, 9], [5, 6], [3], [4, 8], [13]], 
      [[0], [1], [9, 5, 6], [3], [4, 8], [2], [13]], 
      [[0, 1, 9, 5, 6, 3], [4, 8], [2], [13]], 
      [1, 3], 
      [[0], [9, 5, 6, 3, 4, 8], [2], [13]]
    ]
    

    Misalnya baris pertama berarti bahwa pada awal grafik, T-Rex adalah satu-satunya, tiga Raptor bersama-sama, Malcolm sendirian, Grant dan Sattler bersama-sama, dll. Acara kedua ke terakhir berarti dua Raptors mati. .

    Bagaimana tepatnya Anda mengharapkan input terserah Anda, selama informasi seperti ini dapat ditentukan. Misalnya, Anda dapat menggunakan format daftar yang mudah. Anda juga dapat mengharapkan karakter dalam acara tersebut menjadi nama karakter lengkap lagi dll.

    Anda dapat (tetapi tidak harus) berasumsi bahwa setiap daftar grup berisi setiap karakter yang hidup tepat dalam satu grup. Namun, Anda tidak boleh berasumsi bahwa grup atau karakter dalam satu acara berada dalam urutan yang nyaman.

  • Render ke layar atau file (sebagai vektor atau grafik raster) grafik yang memiliki satu baris untuk setiap karakter. Setiap baris harus diberi label dengan nama karakter di awal baris.

  • Untuk setiap peristiwa normal, harus ada, secara berurutan, beberapa bagian melintang dari bagan di mana kelompok-kelompok karakter secara jelas mirip dengan kedekatan garis mereka masing-masing.
  • Untuk setiap peristiwa kematian, garis-garis karakter yang relevan harus berakhir dalam gumpalan yang terlihat.
  • Anda tidak harus mereproduksi fitur lain dari plot Randall, Anda juga tidak harus mereproduksi gaya gambarnya. Garis-garis lurus dengan belokan tajam, semuanya hitam, tanpa label lebih lanjut dan judul sangat baik untuk memasuki kompetisi. Anda juga tidak perlu menggunakan ruang secara efisien - misalnya Anda berpotensi menyederhanakan algoritma Anda dengan hanya memindahkan garis ke bawah untuk bertemu dengan karakter lain, selama ada arah waktu yang jelas.

Saya telah menambahkan solusi referensi yang memenuhi persis persyaratan minimum ini.

Membuatnya Cantik

Ini adalah kontes popularitas, jadi di atas itu, Anda dapat menerapkan kesenangan apa pun yang Anda inginkan. Penambahan yang paling penting adalah algoritma tata letak yang layak yang membuat bagan lebih terbaca - misalnya yang membuat tikungan di garis mudah diikuti dan yang mengurangi jumlah penyilangan garis yang diperlukan. Ini adalah masalah algoritmik inti dari tantangan ini! Suara akan memutuskan seberapa baik kinerja algoritma Anda dalam menjaga grafik tetap rapi.

Tapi di sini ada beberapa ide lagi, sebagian besar berdasarkan pada bagan Randall:

Dekorasi:

  • Garis berwarna.
  • Judul untuk plot.
  • Garis pelabelan berakhir.
  • Secara otomatis relabelling baris yang telah melalui bagian yang sibuk.
  • Gaya digambar tangan (atau lainnya? Seperti yang saya katakan, tidak perlu mereproduksi gaya Randall jika Anda memiliki ide yang lebih baik) untuk garis dan font.
  • Orientasi sumbu waktu yang dapat disesuaikan.

Ekspresifitas tambahan:

  • Disebutkan peristiwa / kelompok / kematian.
  • Menghilang dan muncul kembali garis.
  • Karakter masuk terlambat.
  • Menyoroti yang menunjukkan (dapat dipindahtangankan) properti karakter (misalnya, lihat ringbearer di bagan LotR).
  • Pengkodean informasi tambahan dalam sumbu pengelompokan (mis. Informasi geografis seperti pada bagan LotR).
  • Perjalanan waktu?
  • Realitas alternatif?
  • Karakter berubah menjadi yang lain?
  • Penggabungan dua karakter? (Pemisahan karakter?)
  • 3D? (Jika Anda benar-benar melangkah sejauh itu, pastikan Anda benar-benar menggunakan dimensi tambahan untuk memvisualisasikan sesuatu!)
  • Fitur relevan lainnya, yang dapat berguna untuk memvisualisasikan narasi film (atau buku, dll.).

Tentu saja, banyak dari ini akan memerlukan input tambahan, dan Anda bebas untuk menambah format input Anda seperlunya, tetapi harap dokumentasikan bagaimana data dapat dimasukkan.

Harap sertakan satu atau dua contoh untuk memamerkan fitur yang Anda terapkan.

Solusi Anda harus dapat menangani input yang valid, tetapi tidak apa-apa jika lebih cocok untuk jenis narasi tertentu daripada yang lain.

Kriteria Pemilihan

Saya tidak punya ilusi bahwa saya bisa memberi tahu orang-orang bagaimana mereka harus menggunakan suara mereka, tetapi di sini ada beberapa pedoman yang disarankan sesuai kepentingan:

  • Turunkan jawaban yang mengeksploitasi celah, yang standar atau yang lain, atau hardcode satu atau lebih hasil.
  • Jangan memvotasikan jawaban yang tidak memenuhi persyaratan minimum (tidak peduli seberapa suka sisanya).
  • Pertama dan terpenting, tingkatkan algoritma tata letak yang bagus. Ini termasuk jawaban yang tidak menggunakan banyak ruang vertikal sambil meminimalkan penyilangan garis agar grafik dapat terbaca, atau yang berhasil menyandikan informasi tambahan ke dalam sumbu vertikal. Memvisualisasikan pengelompokan tanpa membuat kekacauan besar harus menjadi fokus utama dari tantangan ini, sehingga ini tetap menjadi kontes pemrograman dengan masalah algoritme yang menarik.
  • Upvote fitur opsional yang menambah daya ekspresif (yaitu bukan hanya dekorasi murni).
  • Terakhir, upvote presentasi yang bagus.
Martin Ender
sumber
7
karena kode-golf tidak memiliki cukup xkcd
haskeller bangga
8
@proudhaskeller PPCG tidak pernah memiliki cukup xkcd. ;) Tapi saya tidak berpikir kita sudah mencoba untuk membuat tantangan info super besar grafis / visualisasinya, jadi saya harap saya membawa sesuatu yang baru ke meja dengan ini. Dan saya yakin beberapa yang lain akan membuat tantangan yang sangat berbeda dan menarik juga.
Martin Ender
Apakah boleh jika solusi saya hanya menangani 12 pria yang marah, Duel (Spielberg, 1971, pengemudi kendaraan bermotor vs pengemudi truk gila), dan Planes, Train and Automobiles? ;-)
Level River St
4
Saya bertanya-tanya bagaimana input untuk primer akan terlihat seperti ...
Joshua
1
@ping Ya, itu idenya. Jika suatu peristiwa berisi daftar lebih lanjut, itu adalah pengelompokan daftar. jadi [[x,y,z]]artinya semua karakter saat ini bersama-sama. Tetapi jika acara tidak mengandung daftar, tetapi hanya karakter secara langsung, itu adalah kematian, jadi dalam situasi yang sama [x,y,z]berarti ketiga karakter itu mati. Jangan ragu untuk menggunakan format lain, dengan indikasi eksplisit apakah sesuatu itu adalah kematian atau peristiwa pengelompokan jika itu membantu Anda. Format di atas hanya saran. Selama format input Anda setidaknya bersifat ekspresif, Anda dapat menggunakan sesuatu yang lain.
Martin Ender

Jawaban:

18

Python3 dengan numpy, scipy dan matplotlib

Taman jurassic

edit :

  • Saya mencoba untuk menjaga kelompok dalam posisi relatif yang sama antara peristiwa, maka sorted_eventfungsinya.
  • Fungsi baru untuk menghitung posisi y karakter ( coords).
  • Setiap acara hidup diplot dua kali sekarang, sehingga karakter tetap bersatu lebih baik.
  • Menambahkan label legenda dan kapak yang dihapus.
import math
import numpy as np
from scipy.interpolate import interp1d
from matplotlib import cm, pyplot as plt


def sorted_event(prev, event):
    """ Returns a new sorted event, where the order of the groups is
    similar to the order in the previous event. """
    similarity = lambda a, b: len(set(a) & set(b)) - len(set(a) ^ set(b))
    most_similar = lambda g: max(prev, key=lambda pg: similarity(g, pg))
    return sorted(event, key=lambda g: prev.index(most_similar(g)))


def parse_data(chars, events):
    """ Turns the input data into 3 "tables":
    - characters: {character_id: character_name}
    - timelines: {character_id: [y0, y1, y2, ...],
    - deaths: {character_id: (x, y)}
    where x and y are the coordinates of a point in the xkcd like plot.
    """
    characters = dict(enumerate(chars))
    deaths = {}
    timelines = {char: [] for char in characters}

    def coords(character, event):
        for gi, group in enumerate(event):
            if character in group:
                ci = group.index(character)
                return (gi + 0.5 * ci / len(group)) / len(event)
        return None

    t = 0
    previous = events[0]
    for event in events:
        if isinstance(event[0], list):
            previous = event = sorted_event(previous, event)
            for character in [c for c in characters if c not in deaths]:
                timelines[character] += [coords(character, event)] * 2
            t += 2
        else:
            for char in set(event) - set(deaths):
                deaths[char] = (t-1, timelines[char][-1])

    return characters, timelines, deaths


def plot_data(chars, timelines, deaths):
    """ Draws a nice xkcd like movie timeline """

    plt.xkcd()  # because python :)

    fig = plt.figure(figsize=(16,8))
    ax = fig.add_subplot(111)
    ax.get_xaxis().set_visible(False)
    ax.get_yaxis().set_visible(False)
    ax.set_xlim([0, max(map(len, timelines.values()))])

    color_floats = np.linspace(0, 1, len(chars))
    color_of = lambda char_id: cm.Accent(color_floats[char_id])

    for char_id in sorted(chars):
        y = timelines[char_id]
        f = interp1d(np.linspace(0, len(y)-1, len(y)), y, kind=5)
        x = np.linspace(0, len(y)-1, len(y)*10)
        ax.plot(x, f(x), c=color_of(char_id))

    x, y = zip(*(deaths[char_id] for char_id in sorted(deaths)))
    ax.scatter(x, y, c=np.array(list(map(color_of, sorted(deaths)))), 
               zorder=99, s=40)

    ax.legend(list(map(chars.get, sorted(chars))), loc='best', ncol=4)
    fig.savefig('testplot.png')


if __name__ == '__main__':
    chars = [
        "T-Rex","Raptor","Raptor","Raptor","Malcolm","Grant","Sattler",
        "Gennaro","Hammond","Kids","Muldoon","Arnold","Nedry","Dilophosaurus"
    ]
    events = [
        [[0],[1,2,3],[4],[5,6],[7,8,10,11,12],[9],[13]],
        [[0],[1,2,3],[4,7,5,6,8,9,10,11,12],[13]],
        [[0],[1,2,3],[4,7,5,6,8,9,10],[11,12],[13]],
        [[0],[1,2,3],[4,7,5,6,9],[8,10,11,12],[13]],
        [[0,4,7],[1,2,3],[5,9],[6,8,10,11],[12],[13]],
        [7],
        [[5,9],[0],[4,6,10],[1,2,3],[8,11],[12,13]],
        [12],
        [[0,5,9],[1,2,3],[4,6,10,8,11],[13]],
        [[0],[5,9],[1,2],[3,11],[4,6,10,8],[13]],
        [11],
        [[0],[5,9],[1,2,10],[3,6],[4,8],[13]],
        [10],
        [[0],[1,2,9],[5,6],[3],[4,8],[13]],
        [[0],[1],[9,5,6],[3],[4,8],[2],[13]],
        [[0,1,9,5,6,3],[4,8],[2],[13]],
        [1,3],
        [[0],[9,5,6,3,4,8],[2],[13]]
    ]
    plot_data(*parse_data(chars, events))
pgy
sumber
Hah, tampilan xkcd yang sangat bagus:) ... ada kemungkinan Anda bisa memberi label pada baris?
Martin Ender
Beri label pada garis, memiliki lebar garis yang berbeda (dengan penurunan / peningkatan antara beberapa titik) dan akhirnya ... buat garis lebih horizontal ketika dekat ke titik sementara interpolasi, lebih seperti kurva bezier dan ini akan menjadi entri IMO terbaik: )
Pengoptimal
1
Terima kasih, tetapi gaya xkcd termasuk dalam matplotlib, jadi itu hanya panggilan fungsi :) Yah, saya membuat legenda, tetapi menempati hampir sepertiga dari gambar, jadi saya berkomentar.
pgy
Saya mengubah jawaban saya, saya pikir itu terlihat lebih baik sekarang.
pgy
6

T-SQL

Saya tidak senang dengan ini sebagai entri, tapi saya pikir pertanyaan ini layak untuk dicoba setidaknya. Saya akan mencoba untuk memperbaikinya nanti, tetapi pelabelan akan selalu menjadi masalah dalam SQL. Solusinya memerlukan SQL 2012+ dan dijalankan di SSMS (SQL Server Management Studio). Outputnya ada di tab hasil spasial.

-- Variables for the input
DECLARE @actors NVARCHAR(MAX) = '["T-Rex", "Raptor", "Raptor", "Raptor", "Malcolm", "Grant", "Sattler", "Gennaro", "Hammond", "Kids", "Muldoon", "Arnold", "Nedry", "Dilophosaurus"]';
DECLARE @timeline NVARCHAR(MAX) = '
[
   [[1], [2, 3, 4], [5], [6, 7], [8, 9, 11, 12, 13], [10], [14]],
   [[1], [2, 3, 4], [5, 8, 6, 7, 9, 10, 11, 12, 13], [14]],
   [[1], [2, 3, 4], [5, 8, 6, 7, 9, 10, 11], [12, 13], [14]],
   [[1], [2, 3, 4], [5, 8, 6, 7, 10], [9, 11, 12, 13], [14]],
   [[1, 5, 8], [2, 3, 4], [6, 10], [7, 9, 11, 12], [13], [14]],
   [8],
   [[6, 10], [1], [5, 7, 11], [2, 3, 4], [9, 12], [13, 14]],
   [13],
   [[1, 6, 10], [2, 3, 4], [5, 7, 11, 9, 12], [14]],
   [[1], [6, 10], [2, 3], [4, 12], [5, 7, 11, 9], [14]],
   [12],
   [[1], [6, 10], [2, 3, 11], [4, 7], [5, 9], [14]],
   [11],
   [[1], [2, 3, 10], [6, 7], [4], [5, 9], [14]],
   [[1], [2], [10, 6, 7], [4], [5, 9], [3], [14]],
   [[1, 2, 10, 6, 7, 4], [5, 9], [3], [14]],
   [2, 4],
   [[1], [10, 6, 7, 5, 9], [3], [14]]
]
';

-- Populate Actor table
WITH actor(A) AS ( SELECT CAST(REPLACE(STUFF(REPLACE(REPLACE(@actors,', ',','),'","','</a><a>'),1,2,'<a>'),'"]','</a>') AS XML))
SELECT ROW_NUMBER() OVER (ORDER BY(SELECT \)) ActorID, a.n.value('.','varchar(50)') Name
INTO Actor
FROM actor CROSS APPLY A.nodes('/a') as a(n);

-- Populate Timeline Table
WITH Seq(L) AS (
    SELECT CAST(REPLACE(REPLACE(REPLACE(REPLACE(@timeline,'[','<e>'),']','</e>'),'</e>,<e>','</e><e>'),'</e>,','</e>') AS XML)
    ),
    TimeLine(N,Exerpt,Elem) AS (
    SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) N
        ,z.query('.')
        ,CAST(REPLACE(CAST(z.query('.') AS VARCHAR(MAX)),',','</e><e>') AS XML)
    FROM Seq 
        CROSS APPLY Seq.L.nodes('/e/e') AS Z(Z)
    ),
    Groups(N,G,Exerpt) AS (
    SELECT N, 
        ROW_NUMBER() OVER (PARTITION BY N ORDER BY CAST(SUBSTRING(node.value('.','varchar(50)'),1,ISNULL(NULLIF(CHARINDEX(',',node.value('.','varchar(50)')),0),99)-1) AS INT)), 
        CAST(REPLACE(CAST(node.query('.') AS VARCHAR(MAX)),',','</e><e>') AS XML) C
    FROM TimeLine 
        CROSS APPLY Exerpt.nodes('/e/e') as Z(node)
    WHERE Exerpt.exist('/e/e') = 1
    )
SELECT * 
INTO TimeLine
FROM (
    SELECT N, null G, null P, node.value('.','int') ActorID, 1 D 
    FROM TimeLine CROSS APPLY TimeLine.Elem.nodes('/e') AS E(node)
    WHERE Exerpt.exist('/e/e') = 0
    UNION ALL
    SELECT N, G, DENSE_RANK() OVER (PARTITION BY N, G ORDER BY node.value('.','int')), node.value('.','int') ActorID, 0
    FROM Groups CROSS APPLY Groups.Exerpt.nodes('/e') AS D(node)
    ) z;

-- Sort the entries again
WITH ReOrder AS (
            SELECT *, 
                ROW_NUMBER() OVER (PARTITION BY N,G ORDER BY PG, ActorID) PP, 
                COUNT(P) OVER (PARTITION BY N,G) CP, 
                MAX(G) OVER (PARTITION BY N) MG, 
                MAX(ActorID) OVER (ORDER BY (SELECT\)) MA
            FROM (
                SELECT *,
                    LAG(G,1) OVER (PARTITION BY ActorID ORDER BY N) PG,
                    LEAD(G,1) OVER (PARTITION BY ActorID ORDER BY N) NG
                FROM timeline
                ) rg
    )
SELECT * INTO Reordered
FROM ReOrder;
ALTER TABLE Reordered ADD PPP INT
GO
ALTER TABLE Reordered ADD LPP INT
GO
WITH U AS (SELECT N, P, LPP, LAG(PP,1) OVER (PARTITION BY ActorID ORDER BY N) X FROM Reordered)
UPDATE U SET LPP = X FROM U;
WITH U AS (SELECT N, ActorID, P, PG, LPP, PPP, DENSE_RANK() OVER (PARTITION BY N,G ORDER BY PG, LPP) X FROM Reordered)
UPDATE U SET PPP = X FROM U;
GO

SELECT Name, 
    Geometry::STGeomFromText(
        STUFF(LS,1,2,'LINESTRING (') + ')'
        ,0)
        .STBuffer(.1)
        .STUnion(
        Geometry::STGeomFromText('POINT (' + REVERSE(SUBSTRING(REVERSE(LS),1,CHARINDEX(',',REVERSE(LS))-1)) + ')',0).STBuffer(D*.4)
        )
FROM Actor a
    CROSS APPLY (
        SELECT CONCAT(', '
            ,((N*5)-1.2)
                ,' ',(G)+P
            ,', '
            ,((N*5)+1.2)
                ,' ',(G)+P 
            ) AS [text()]
        FROM (
            SELECT ActorID, N,
                CASE WHEN d = 1 THEN
                    ((MA+.0) / (LAG(MG,1) OVER (PARTITION BY ActorID ORDER BY N)+.0)) * 
                    PG * 1.2
                ELSE 
                    ((MA+.0) / (MG+.0)) * 
                    G * 1.2
                END G,
                CASE WHEN d = 1 THEN
                (LAG(PPP,1) OVER (PARTITION BY ActorID ORDER BY N) -((LAG(CP,1) OVER (PARTITION BY ActorID ORDER BY N)-1)/2)) * .2 
                ELSE
                (PPP-((CP-1)/2)) * .2 
                END P
                ,PG
                ,NG
            FROM Reordered
            ) t
        WHERE a.actorid = t.actorid
        ORDER BY N, G
        FOR XML PATH('')
        ) x(LS)
    CROSS APPLY (SELECT MAX(D) d FROM TimeLine dt WHERE dt.ActorID = a.ActorID) d
GO

DROP TABLE Actor;
DROP TABLE Timeline;
DROP TABLE Reordered;

Timeline yang dihasilkan terlihat seperti berikut ini masukkan deskripsi gambar di sini

MickyT
sumber
4

Mathematica, Solusi Referensi

Sebagai referensi, saya menyediakan skrip Mathematica yang memenuhi persis persyaratan minimum, tidak lebih, tidak kurang.

Itu mengharapkan karakter menjadi daftar format dalam pertanyaan dalam chars, dan peristiwa dalam events.

n = Length@chars;
m = Max@Map[Length, events, {2}];
deaths = {};
Graphics[
 {
  PointSize@Large,
  (
     linePoints = If[Length@# == 3,
         lastPoint = {#[[1]], #[[2]] + #[[3]]/(m + 2)},
         AppendTo[deaths, Point@lastPoint]; lastPoint
         ] & /@ Position[events, #];
     {
      Line@linePoints,
      Text[chars[[#]], linePoints[[1]] - {.5, 0}]
      }
     ) & /@ Range@n,
  deaths
  }
 ]

Sebagai contoh, berikut adalah contoh Jurassic Park menggunakan jenis daftar Mathematica:

chars = {"T-Rex", "Raptor", "Raptor", "Raptor", "Malcolm", "Grant", 
   "Sattler", "Gennaro", "Hammond", "Kids", "Muldoon", "Arnold", 
   "Nedry", "Dilophosaurus"};
events = {
   {{1}, {2, 3, 4}, {5}, {6, 7}, {8, 9, 11, 12, 13}, {10}, {14}},
   {{1}, {2, 3, 4}, {5, 8, 6, 7, 9, 10, 11, 12, 13}, {14}},
   {{1}, {2, 3, 4}, {5, 8, 6, 7, 9, 10, 11}, {12, 13}, {14}},
   {{1}, {2, 3, 4}, {5, 8, 6, 7, 10}, {9, 11, 12, 13}, {14}},
   {{1, 5, 8}, {2, 3, 4}, {6, 10}, {7, 9, 11, 12}, {13}, {14}},
   {8},
   {{6, 10}, {1}, {5, 7, 11}, {2, 3, 4}, {9, 12}, {13, 14}},
   {13},
   {{1, 6, 10}, {2, 3, 4}, {5, 7, 11, 9, 12}, {14}},
   {{1}, {6, 10}, {2, 3}, {4, 12}, {5, 7, 11, 9}, {14}},
   {12},
   {{1}, {6, 10}, {2, 3, 11}, {4, 7}, {5, 9}, {14}},
   {11},
   {{1}, {2, 3, 10}, {6, 7}, {4}, {5, 9}, {14}},
   {{1}, {2}, {10, 6, 7}, {4}, {5, 9}, {3}, {14}},
   {{1, 2, 10, 6, 7, 4}, {5, 9}, {3}, {14}},
   {2, 4},
   {{1}, {10, 6, 7, 4, 5, 9}, {3}, {14}}
};

kita akan mendapatkan:

masukkan deskripsi gambar di sini

(Klik untuk versi yang lebih besar.)

Itu tidak terlihat terlalu buruk, tetapi itu terutama karena data input lebih atau kurang teratur. Jika kami mengocok grup dan karakter di setiap acara (sambil mempertahankan struktur yang sama), hal-hal seperti ini dapat terjadi:

masukkan deskripsi gambar di sini

Yang agak berantakan.

Jadi seperti yang saya katakan, ini hanya memenuhi persyaratan minimum. Itu tidak mencoba untuk menemukan tata letak yang bagus dan tidak cantik, tetapi di situlah kalian masuk!

Martin Ender
sumber
Saya hanya berpikir Anda mungkin bisa 'cantik' itu dengan menggunakan kuadratik kuadrat atau kubik untuk menghapus sudut yang tajam? (Saya akan melakukannya dengan cara itu garis singgung pada poin yang diberikan selalu 0)
flawr
@ flawr Tentu, atau saya bisa menerapkan beberapa trik ini , tapi itu bukan tujuan dari jawaban ini. ;) Saya benar-benar hanya ingin memberikan referensi untuk minimum absolut.
Martin Ender
3
Oh maaf, bahkan tidak menyadari bahwa ini adalah pertanyaan Anda sendiri = P
flawr