Mpld3: Navigateur/HTML spécifiez la taille de la figure plutôt que figsize * dpi

Créé le 6 févr. 2014  ·  31Commentaires  ·  Source: mpld3/mpld3

J'apprécie vraiment le paquet, merci beaucoup pour le partage.

C'est une suggestion d'amélioration, mais ce n'est pas tout à fait pythonique. Mais là encore, d3js non plus :)

C'est formidable que la taille définie de la figure soit reflétée à l'écran, mais je pense qu'il serait "plus réactif" d'avoir la possibilité de définir la taille de la toile sur la div englobante, puis de laisser cette taille de div être déterminée par la largeur du navigateur / peu importe sinon le développeur veut.

Est-ce que ça vaut la peine que je m'occupe de _js.py et _object.py pour que ça marche ?

Commentaire le plus utile

@cliffckerr Je peux certainement ajouter ceci à draw_figure , peut-être comme argument qui prend un Object et définit tous les attributs spécifiés dans cet objet sur le svg , afin que vous puissiez passer dans quelque chose comme {viewBox: ..., width: '90vw', height: 'auto'} .

Je voulais vous demander, à vous et aux autres dans ce fil, si vous pensez que cela devrait être ajouté ailleurs, pour prendre en charge par exemple fig_to_html() . Si c'est le cas, nous devrions peut-être réfléchir à une solution à cela. Sinon, faites-le moi savoir et je pourrai immédiatement apporter les modifications à draw_figure() .

Tous les 31 commentaires

Je ne suis pas tout à fait sûr de ce que vous demandez ... Nous obtenons la taille de la figure en pouces à partir de la figure matplotlib, et obtenons également le dpi à partir de la figure matplotlib. Comment suggérez-vous que nous changions cela ? Merci

Plutôt que d'utiliser la taille de figure absolue définie en Python, je veux dire quelque chose comme ceci :

<div id='parent' class='figsizeinfo'>
//mpld3 generated code
</div>

Où figsizeinfo est une spécification CSS de taille, et le mpld3 remplit la div. (d'après mon expérience, c'est ainsi que fonctionne flotjs)

Hmm ... cette question pourrait trahir mon inexpérience avec les trucs Web, mais je pensais que SVG (qui utilise mpld3) est entièrement basé sur les pixels, et cela ne peut pas être facilement changé. Est-ce que flotjs utilise SVG ou une autre spécification ?

Les graphiques SVG (Scalable Vector Format) sont basés sur des vecteurs et non sur des pixels.

Voici un exemple d'image SVG générée avec vanilla matplotlib qui s'adapte à la taille de la fenêtre :

http://bleepshow.pithy.io/SVG_Example

(clairement riffé sur votre exemple)

d3js, et toutes les figures de dessin sur toile, sont vectorielles pour autant que je sache, elles devraient donc pouvoir le faire.

L'astuce, j'imagine, est de mettre

figwidth = $(“#parent”).width //(jquery based, but you get the point )

plutôt que

figwidth = figsize[0] * dpi;

Oui, je sais que SVG est vectoriel, mais sauf erreur de ma part, la taille de la toile, ainsi que la taille des marqueurs et leur position sur l'écran sont mesurées en points. Il semble qu'il serait extrêmement compliqué d'écrire du code SVG qui sera correctement mis à l'échelle pour un chiffre arbitraire avec une taille de fenêtre arbitraire.

Non, c'est assez facile.

Voici le hack que j'ai appliqué à votre code fig_to_d3 :

print mpld3.fig_to_d3(fig).replace("\n","\r").replace("var figwidth = 8.0 * 80","var figwidth = $(window).width()*.9;").replace("var figheight = 6.0 * 80","var figheight = $(window).height()*.9;")

qui se présente ainsi :

http://bleepshow.pithy.io/mpld3_window_scale_test

Maintenant, les étiquettes sont au mauvais endroit, mais encore une fois, c'est "juste" une question de référence de leur position à la div et pas absolument à la figure. Mais le rendu de la figure fonctionne bien pour autant que je sache.

(notez que cela ne se redimensionne que si vous actualisez, mais avec un peu de colle qui est également simple)

Je suis actuellement en train de créer une solution basée sur cela pour moi-même, je suis heureux d'essayer de la peaufiner en quelque chose de décent pour une pull-request.

La taille réactive de la figure mpld3 sera vraiment utile. Si vous l'ajoutez avec un bouton (dans la barre d'outils) pour pouvoir ouvrir la figure actuelle dans une nouvelle fenêtre, la figure entière sera facilement redimensionnable.

+1

@hadim je voudrais le garder "pythonic", donc quelque chose comme

mpld3.fig_to_d3(fig,size="adaptive")

serait un bon déclencheur

Je suis d'accord !

Je comprends que faire de la figure une taille différente est facile. Mais dans l'ensemble, je ne suis toujours pas convaincu que ce soit une bonne idée. Une fois la taille de la figure modifiée, cela impliquera de pirater toutes les largeurs de ligne, les emplacements des marqueurs, les tailles des marqueurs, les tailles de police, etc., ce qui ajoutera beaucoup de complications au code sans grand avantage. De plus, le placement correct de la légende, des étiquettes d'axe et d'autres propriétés dépend de la commande plt.draw (), qui fait des hypothèses basées sur la taille de la figure interne et les paramètres dpi, et honnêtement, je n'ai aucune idée de la façon dont cela pourrait facilement être re -ajusté après coup.

La philosophie de ce mpld3 est de produire des sorties simples qui reflètent le plus fidèlement possible ce qui se trouve dans les chiffres de matplotlib. Si vous souhaitez modifier la taille de la figure de sortie, la méthode préférée consiste à ajuster la taille de la figure matplotlib. Tout le reste semble devenir rapidement un casse-tête majeur à mettre en œuvre, à tester et à entretenir.

Qu'en est-il d'un plugin ?

plugins.connect(fig, plugins.ResponsiveSize())

Un plugin serait probablement un bon moyen de le faire. Mais encore une fois, comment gérez-vous l'adaptation de la taille de la police, de la largeur des lignes, de la taille des marqueurs, des emplacements des légendes et des emplacements du texte des axes ? En raison de tous ces problèmes, sans une infrastructure étendue pour prendre en charge le redimensionnement, je pense qu'un tel plugin aurait une utilité très limitée.

@jakevdp Je ne suis pas d'accord: la seule chose qui a souffert dans mon piratage de 3 secondes ci-dessus, ce sont les emplacements des étiquettes, et encore une fois, il s'agit d'écrire un meilleur javascript. Le DOM permet un positionnement absolu et une position relative sans trop de travail.

La façon dont le code est implémenté maintenant, tout est codé en dur dans la figure. Avec un peu de travail, les largeurs/hauteurs, l'épaisseur des lignes et la taille de la police peuvent être mises à l'échelle linéairement par rapport à la taille div. La partie la plus délicate serait le rapport d'aspect, et cela peut être verrouillé. Une fois le rapport d'aspect verrouillé, la mise à l'échelle linéaire de tout est assez simple

@hadim c'est une bonne idée.

Je ne connais pas d3.js donc je n'ai aucune idée. Mais parce que nous travaillons sur un fichier svg, je n'imagine vraiment pas que nous ne pouvons pas le faire facilement...

Quelques liens pour commencer à réfléchir à la meilleure façon de le faire :

+1 pour @dansteingart. Pourquoi ne pas définir toutes les longueurs et positions internes SVG en coordonnées relatives, puis avant la sortie, utilisez une viewBox pour redimensionner la figure entière et ainsi faire correspondre le paramètre figsize matplotlib (ou être réactif dans un autre cas).

@dansteingart - Je suis d'accord qu'avec suffisamment de hacks JS, vous pourriez le faire fonctionner. Mais quoi qu'il en soit, je ne suis pas favorable à une modification fondamentale de ce package pour inclure cette fonctionnalité.

Pourquoi? Eh bien, d'une part, cela va à l'encontre de la philosophie susmentionnée du package, à savoir que les éléments de tracé, les tailles de figure, etc. doivent être définis via l'interface matplotlib.

Deuxièmement, ce n'est pas une fonctionnalité "indispensable", et rendre la base de code beaucoup plus compliquée pour une telle fonctionnalité non indispensable nuira au package à long terme. Pour savoir pourquoi cela pourrait être, voir le discours de @ellisonbg de l'année dernière à SciPy.

Si vous souhaitez travailler sur un plugin pour ajouter ce type de comportement, vous êtes le bienvenu. Si cela fonctionne bien, cela peut même valoir la peine de l'inclure dans le module plugins . Mais je suis fermement contre les modifications majeures de la disposition de base des figures pour de nouvelles fonctionnalités qui vont à l'encontre de la philosophie du package, aussi cool soient-elles.

Je suis juste en train de me faire prendre à ce sujet, et il me semble qu'une sorte de plugin peut accomplir une sorte de comportement de redimensionnement réactif intéressant. @dansteingart a commencé ce fil avec une idée de fonctionnalité et une question sur l'opportunité de commencer à implémenter la fonctionnalité d'une certaine manière. En réponse à la question, je suggérerais que vous commenciez avec un plugin et que vous voyiez jusqu'où cela peut aller sans changer _objects.py ou _js.py. Mais si vous êtes d'accord, j'aimerais prendre du recul et parler du problème que nous essayons de résoudre :

C'est formidable que la taille définie de la figure soit reflétée à l'écran, mais je pense qu'il serait "plus réactif" d'avoir la possibilité de définir la taille de la toile sur la div englobante, puis de laisser cette taille de div être déterminée par la largeur du navigateur / peu importe sinon le développeur veut.

J'aimerais avoir des éclaircissements sur le cas d'utilisation où cela va se produire. Dans mon flux de travail habituel, j'utilise ipython avec un serveur de bloc-notes exécuté dans le cloud, avec mon navigateur Web maximisé. Donc, cela pourrait me arriver si je passe de mon écran de travail à celui de mon ordinateur portable et que j'ai moins de largeur d'écran. Mais je suis sûr qu'il y a d'autres paramètres auxquels d'autres pensent. Quoi d'autre?

@jakevdp Assez juste, et je suis d'accord qu'il ne devrait rien y avoir de changé _du tout_ du côté python. Cependant, je pense que c'est un jeu équitable du côté de JS, et si le but de l'expérience est de marier les meilleurs aspects de matplotlib et de d3js, je pense que cela vaut la peine de poursuivre.

La façon dont je le vois, une fois que vous appelez fig_to_d3, vous êtes hors de la terre de python, et rendre la figure réactive = écrire mieux JS.

En ce qui concerne l'utilité des pages réactives, pour la sortie iPython seule, je suis d'accord avec vous, mais python est plus qu'un bloc-notes iPython, et je pense qu'en fin de compte, l'utilisation de mpl3d pour créer des pages Web réactives à la taille sera plus utile que vous ne le sous-entendez ici.

Selon votre logique, si je voulais zoomer sur un détail en accord avec matplotlib, je devrais juste changer xlim/ylim.

Encore une fois merci pour le super package, je vais voir ce que je ne peux pas faire avec un plugin et je reviens vers vous.

@aflaxman

J'aimerais avoir des éclaircissements sur le cas d'utilisation où cela va se produire. Dans mon flux de travail habituel, j'utilise ipython avec un serveur de bloc-notes exécuté dans le cloud, avec mon navigateur Web maximisé. Donc, cela pourrait me arriver si je passe de mon écran de travail à celui de mon ordinateur portable et que j'ai moins de largeur d'écran. Mais je suis sûr qu'il y a d'autres paramètres auxquels d'autres pensent. Quoi d'autre?

voir http://pithy.io

Le j'ai écrit concis pour me permettre / mon groupe / mes classes d'écrire des scripts qui se transforment ensuite en applications Web. Faire en sorte que la silhouette s'adapte à la fenêtre rend la vie beaucoup plus agréable. MPL3d me donne des outils beaucoup plus agréables pour faire des tracés interactifs (je piratais autour), et si les tracés interactifs sont sensibles à la taille, tout est gagnant.

Merci @dansteingart. Gardez à l'esprit que même si le JS est caché à l'utilisateur Python, il s'agit toujours de code et il doit encore être maintenu. Mon expérience au fil des ans avec le développement open source est que les gens viennent souvent qui sont enthousiastes à l'idée de mettre en œuvre de nouvelles fonctionnalités, et une fois ces fonctionnalités fusionnées, leur maintenance et leur entretien incombent aux quelques développeurs qui ont investi dans le package dans le long terme. Je suis déjà extrêmement engagé au point où je suis obligé d'ignorer les rapports de bogues sur les packages bien utilisés que j'ai créés il y a quelques années à peine. Je ne veux vraiment pas que mpld3 en arrive à ce point.

Merci de votre compréhension, et j'ai hâte de voir ce que vous pouvez mettre en place dans un plugin.

@jakevdp ce point est bien pris, et je pense que l'architecture du plugin fonctionne bien à cette fin. Si ça casse, ce sera ma faute, pas la vôtre (c'est-à-dire, à moins que vous ne changiez fig_to_d3 :-p )

La réécriture a atterri, et cela pourrait être _légèrement_ plus facile à mettre en œuvre en tant que plugin, du moins pour des situations particulières. Je ne l'ai pas écrit avec ce genre de situation dynamique à l'esprit, donc il peut y avoir quelques pièges. Faites-moi savoir si vous faites des progrès à ce sujet.

Bonjour, merci pour ce super projet. Y a-t-il eu des progrès sur cette question? Le plugin / la fonctionnalité a-t-il été développé ? Plus précisément, je fais référence à "l'option permettant de définir la taille du canevas sur la div englobante". Merci.

Je pense que c'est une fonctionnalité intéressante, mais pour référence après le traçage, vous pouvez utiliser d3 pour mettre la figure à l'échelle automatique (ici avec la hauteur de la fenêtre)

fetch("d3/" + figure + ".json")
    .then((response) => response.json())
    .then((data) => mpld3.draw_figure(figure, data))
    .then(() => {
        d3.select("#" + figure)
            .selectAll(".mpld3-figure")
            .attr("width", "100%")
            .attr("height", "70vh")
            .attr("viewBox", "0 0 1200 800")
            .attr("preserveAspectRatio", "xMinYMin meet");
    });

Ici, je vais seconder @hadim et @carlinmack pour utiliser viewBox . Cela peut être très simple en fait. Par exemple, si vous avez actuellement <svg width="1200" height="800"> , essayez plutôt <svg viewBox="0 0 1200 800"> . C'est ça. Le viewBox définit le système de coordonnées à l'intérieur du svg, et les paramètres width et height deviennent facultatifs et définissent les dimensions externes comme tout autre élément. Vous pouvez également avoir width=100% ou définir directement la valeur via css (la largeur et la hauteur en tant que css ont priorité sur les attributs).

Je l'ai utilisé avec succès avec une version récente de chrome. La dépendance entre navigateurs devrait être étudiée pour les détails, mais je pense que le résultat est clair.

PS : il y a un article de fond sur https://css-tricks.com/scale-svg mais je trouve cela déroutant lorsque la méthode que j'ai décrite ci-dessus fonctionne (au moins dans les navigateurs modernes) et est conceptuellement simple : viewBox = "0 0 ${width} ${height}" pour le code interne, width=... et height=... comme attribut svg ou paramètre css pour le redimensionnement, si nécessaire.

Ouais, c'est exactement ce que @carlinmack a suggéré. J'étais confus par "utiliser d3 pour faire la mise à l'échelle automatique de la figure", car c'est du pur HTML / CSS, mais ok, manipuler le DOM est ce que fait d3. Le "preserveAspectRatio" comme indiqué par @carlinmack est la valeur par défaut je crois (du moins c'est ce qui est expliqué dans le lien css-tricks.com). Pourquoi indiqueriez-vous height="70vh" si le rapport d'aspect est quand même conservé ? Est-ce une sorte de limite supérieure pour les chiffres très étroits et grands ?

correct, je ne voulais pas que les portraits soient plus hauts que la fenêtre :)

Sauf que cela rend le chiffre plus grand que nécessaire, ce qui n'est pas approprié lorsqu'un ajustement serré avec le contenu environnant est nécessaire. Utiliser max-height la place fonctionne pour moi.

Voici comment je finis par charger les chiffres mpld3 dans le navigateur :

      let figureID = "id_12345" ; 
      let dataURL = figureID + ".json" ; 

      d3.json(dataURL).then( function(data) {
        mpld3.draw_figure(figureID, data);
        return data;
      }).then( function(data) {
        d3.select("#" + figureID)
          .selectAll(".mpld3-figure")
          .attr("width", null)   // remove "width" and "height" attributes as set by mpld3
          .attr("height", null)  
          .attr("viewBox", `0 0 ${data.width} ${data.height}`)
      });

Merci pour la discussion @carlinmack @perrette ! Par coïncidence, nous venons de rencontrer cela aussi, la solution de @dkong-idm était :

    const resizeChart = (viewBoxSettings) => {
        let svg = document.getElementsByClassName("mpld3-figure");

        if (svg && svg.length > 0) {
            svg[0].setAttribute("viewBox",viewBoxSettings );
            svg[0].setAttribute("width", "90vw");
            svg[0].setAttribute("height", "auto");
        }
    }

@vladh , cela demanderait-il beaucoup de travail d'ajouter cette fonctionnalité de mise à l'échelle automatique à mpld3 , soit en tant qu'argument facultatif à draw_figure , soit en tant que fonction autonome ?

@cliffckerr Je peux certainement ajouter ceci à draw_figure , peut-être comme argument qui prend un Object et définit tous les attributs spécifiés dans cet objet sur le svg , afin que vous puissiez passer dans quelque chose comme {viewBox: ..., width: '90vw', height: 'auto'} .

Je voulais vous demander, à vous et aux autres dans ce fil, si vous pensez que cela devrait être ajouté ailleurs, pour prendre en charge par exemple fig_to_html() . Si c'est le cas, nous devrions peut-être réfléchir à une solution à cela. Sinon, faites-le moi savoir et je pourrai immédiatement apporter les modifications à draw_figure() .

@vladh Oui, ce serait également très utile dans fig_to_html() .

Cette page vous a été utile?
0 / 5 - 0 notes