Créer des graphiques narratifs de style xkcd

45

Dans l’une des bandes les plus emblématiques de xkcd, Randall Munroe a visualisé les chronologies de plusieurs films dans des tableaux narratifs:

entrez la description de l'image ici (Cliquez pour agrandir.)

Source: xkcd n ° 657 .

Étant donné une spécification de la chronologie d'un film (ou d'un autre récit), vous devez générer un tel graphique. Ceci est un concours de popularité, donc la réponse avec le plus grand nombre de votes (nets) l'emportera.

Exigences minimales

Pour resserrer un peu les spécifications, voici l'ensemble minimal de fonctionnalités que chaque réponse doit implémenter:

  • Prenez en entrée une liste de noms de personnages, suivie d'une liste d'événements. Chaque événement est soit une liste de caractères mourants, soit une liste de groupes de caractères (indiquant quels caractères sont actuellement ensemble). Voici un exemple de codage du récit de Jurassic Park:

    ["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]]
    ]
    

    Par exemple, la première ligne signifie qu'au début du graphique, T-Rex est un seul, les trois rapaces sont ensemble, Malcolm est seul, Grant et Sattler sont ensemble, etc. L'avant-dernier événement signifie que deux des rapaces meurent. .

    Vous vous attendez à ce que l'entrée corresponde exactement à votre choix, à condition que ce type d'information puisse être spécifié. Par exemple, vous pouvez utiliser n'importe quel format de liste pratique. Vous pouvez également vous attendre à ce que les personnages des événements reprennent le nom complet du personnage, etc.

    Vous pouvez (mais ne devez pas nécessairement) supposer que chaque liste de groupes contient chaque personnage vivant dans exactement un groupe. Cependant, vous devriez ne pas supposer que les groupes ou personnages dans un événement sont en ordre particulièrement pratique.

  • Rendez à l'écran ou au fichier (sous forme de vecteur ou de graphique tramé) un graphique comportant une ligne pour chaque caractère. Chaque ligne doit être étiquetée avec un nom de caractère au début de la ligne.

  • Pour chaque événement normal, il doit exister, dans l’ordre, une section du graphique dans laquelle les groupes de caractères se ressemblent nettement par la proximité de leurs lignes respectives.
  • Pour chaque événement de mort, les lignes des caractères pertinents doivent se terminer par un blob visible.
  • Vous n'avez pas à reproduire d'autres caractéristiques des parcelles de Randall, ni à reproduire son style de dessin. Des lignes droites avec des virages serrés, toutes en noir, sans autres étiquettes et un titre est parfaitement acceptable pour entrer dans la compétition. Il n’est pas non plus nécessaire d’utiliser efficacement l’espace - par exemple, vous pourriez potentiellement simplifier votre algorithme en ne déplaçant jamais les lignes vers le bas pour rencontrer d’autres caractères, à condition que la direction du temps soit perceptible.

J'ai ajouté une solution de référence qui répond exactement à ces exigences minimales.

Le rendre joli

Cependant, il s’agit d’un concours de popularité, vous pouvez donc mettre en œuvre toutes les fantaisies que vous souhaitez. L'ajout le plus important est un algorithme de mise en page décent qui rend la carte plus lisible - par exemple, qui permet de suivre facilement les courbures dans les lignes et qui réduit le nombre de croisements de lignes nécessaires. C'est le problème algorithmique de base de ce défi! Les votes détermineront les performances de votre algorithme pour maintenir le graphique en ordre.

Mais voici quelques idées supplémentaires, la plupart basées sur les graphiques de Randall:

Décorations:

  • Lignes colorées.
  • Un titre pour l'intrigue.
  • La ligne d'étiquetage se termine.
  • Les étiquettes qui ont été réétiquetées automatiquement et qui sont passées par une section occupée.
  • Style dessiné à la main (ou autre? Comme je l'ai dit, il n'est pas nécessaire de reproduire le style de Randall si vous avez une meilleure idée) pour les lignes et les polices.
  • Orientation personnalisable de l'axe des temps.

Expressivité supplémentaire:

  • Événements / groupes / décès nommés.
  • Lignes disparaissant et réapparaissant.
  • Caractères entrant en retard.
  • Faits saillants qui indiquent les propriétés (transférables?) Des caractères (par exemple, voir le porteur de sonnerie dans le tableau LotR).
  • Encodage d'informations supplémentaires dans l'axe de regroupement (par exemple, des informations géographiques comme dans le graphique LotR).
  • Voyage dans le temps?
  • Des réalités alternatives?
  • Un personnage en train de devenir un autre?
  • Deux personnages se confondant? (Un personnage se dédoublant?)
  • 3D? (Si vous allez vraiment aussi loin, assurez-vous d'utiliser la dimension supplémentaire pour visualiser quelque chose!)
  • Tout autre élément pertinent pouvant être utile pour visualiser le récit d'un film (ou d'un livre, etc.).

Bien sûr, beaucoup d’entre elles nécessiteront une entrée supplémentaire, et vous êtes libre d’augmenter votre format d’entrée au besoin, mais veuillez indiquer comment les données peuvent être entrées.

Veuillez inclure un ou deux exemples pour montrer les fonctionnalités que vous avez implémentées.

Votre solution devrait pouvoir traiter n'importe quelle entrée valide, mais elle convient parfaitement si elle convient mieux à certains types de récits que d'autres.

Critères de vote

Je ne me fais pas d'illusions, je pourrais dire aux gens comment ils devraient dépenser leurs votes, mais voici quelques recommandations suggérées par ordre d'importance:

  • Les réponses négatives qui exploitent des échappatoires, standard ou autres, ou codent en dur un ou plusieurs résultats.
  • Ne pas upvote les réponses qui ne remplissent pas les exigences minimales (peu importe la fantaisie du reste).
  • D'abord et avant tout, optimisez les algorithmes de mise en page. Cela inclut les réponses qui n'utilisent pas beaucoup d'espace vertical tout en minimisant le croisement des lignes pour que le graphique reste lisible, ou qui parviennent à coder des informations supplémentaires dans l'axe vertical. Visualiser les groupes sans causer de gros dégâts devrait être l’objectif principal de ce défi, de sorte qu’il reste un concours de programmation avec un problème algorithmique intéressant à cœur.
  • Upvote fonctions optionnelles qui ajoutent un pouvoir expressif (c’est-à-dire ne sont pas simplement de la décoration pure).
  • Enfin, upvote belle présentation.
Martin Ender
la source
7
car code-golf n'a pas assez de xkcd
fier haskeller
8
@proudhaskeller PPCG ne peut jamais avoir assez de xkcd. ;) Mais je ne pense pas que nous ayons encore essayé de contester ses graphiques / visualisations d’informations surdimensionnées, alors j’espère que j’apporte quelque chose de nouveau à la table avec cela. Et je suis sûr que certains des autres créeraient également des défis très différents et intéressants.
Martin Ender
Est-ce correct si ma solution ne prend en charge que 12 hommes en colère, Duel (Spielberg, 1971, automobiliste traditionnel vs camionneur fou), et des avions, des trains et des automobiles? ;-)
Level River St
4
Je me demande comment serait l'entrée pour l'amorce ...
Joshua
1
@ping Oui, c'était l'idée. Si un événement contient d'autres listes, il s'agit d'une liste de groupes. cela [[x,y,z]]voudrait dire que tous les personnages sont actuellement ensemble. Mais si l'événement ne contient pas de listes, mais uniquement des personnages directement, c'est même une mort, alors dans la même situation [x,y,z], ces trois personnages meurent. N'hésitez pas à utiliser un autre format, avec une indication explicite de savoir si quelque chose est un événement de décès ou de regroupement si cela vous aide. Le format ci-dessus n'est qu'une suggestion. Tant que votre format de saisie est au moins aussi expressif, vous pouvez utiliser autre chose.
Martin Ender

Réponses:

18

Python3 avec numpy, scipy et matplotlib

parc jurassique

éditer :

  • J'ai essayé de garder les groupes dans la même position relative entre les événements, d'où la sorted_eventfonction.
  • Nouvelle fonction permettant de calculer la position y des caractères ( coords).
  • Chaque événement vivant est tracé deux fois maintenant, donc les personnages collent mieux ensemble.
  • Légende ajoutée et libellé des axes supprimés.
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
la source
Hah, très beau look xkcd:) ... une chance que tu puisses étiqueter les lignes?
Martin Ender
Étiquetez les lignes, avez des largeurs de lignes différentes (décroissant / augmentant entre certains points) et enfin ... rendez les lignes plus horizontales lorsqu'elles se rapprochent d'un sommet tout en interpolant, ressemblant davantage à une courbe de Bézier et ce serait la meilleure entrée IMO: )
Optimiseur
1
Merci, mais le style xkcd est inclus dans matplotlib, donc ce n'était qu'un appel de fonction :) Bien, j'ai créé une légende, mais elle occupe près du tiers de l'image, je l'ai donc commentée.
pgy
J'ai modifié ma réponse, je pense que ça a l'air mieux maintenant.
PGY
6

T-SQL

Je ne suis pas content que cela soit une entrée, mais je pense que cette question mérite au moins un essai. Je vais essayer d’améliorer cette situation plus tard, mais l’étiquetage restera toujours un problème en SQL. La solution nécessite SQL 2012+ et est exécutée dans SSMS (SQL Server Management Studio). La sortie se trouve dans l'onglet de résultats spatiaux.

-- 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;

Le calendrier qui en résulte ressemble à ce qui suit entrez la description de l'image ici

MickyT
la source
4

Mathematica, solution de référence

Pour référence, je fournis un script Mathematica qui remplit exactement les exigences minimales, rien de plus, rien de moins.

Il s'attend à ce que les caractères soient une liste du format de la question charset des événements 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
  }
 ]

À titre d'exemple, voici l'exemple de Jurassic Park utilisant le type de liste de 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}}
};

nous aurons:

entrez la description de l'image ici

(Cliquez pour agrandir.)

Cela ne regarde pas trop mal, mais c'est surtout parce que les données d'entrée est plus ou moins ordonné. Si nous mélangeons les groupes et les personnages dans chaque événement (tout en conservant la même structure), cela peut arriver:

entrez la description de l'image ici

Ce qui est un peu le bordel.

Donc, comme je l'ai dit, cela ne remplit que les exigences minimales. Il n'essaie pas de trouver une belle mise en page et ce n'est pas joli, mais c'est là que vous entrez!

Martin Ender
la source
Je pensais juste que vous pourriez peut-être «embellir» en utilisant des splines quadratiques ou cubiques afin de supprimer les angles aigus? (Je le ferais pour que la tangente aux points donnés soit toujours
égale à
@flawr Bien sûr, ou je pourrais appliquer certaines de ces astuces , mais ce n'était pas le but de cette réponse. ;) Je voulais vraiment juste fournir une référence pour le minimum absolu.
Martin Ender
3
Oh désolé,
je