1 // ====== I18N ====== 2 let CURRENT_LANG = localStorage.getItem('lang') || 'en'; 3 function getDataFileForLang(lang) { 4 return (lang === 'es') ? 'goya_network_ES.json' : 'goya_network.json'; 5 } 6 7 const I18N = { 8 en: { 9 BTN_NETWORK:"Network", BTN_ABOUT:"About", BTN_PEOPLE:"People", BTN_PARTNERS:"Partners", 10 BTN_ARTWORKS:"Goya's work", BTN_BIB:"Bibliography", BTN_CITE:"How to cite", BTN_MENTIONS:"Mentions", BTN_NEWS:"News", BTN_RELEVANT_LINKS:"Relevant links", BTN_CONTACT:"Contact", 11 FUNDING_PARTNERS: "Funding Partners", 12 ACADEMIC_PARTNERS: "Academic Partners", 13 GERDA_HENKEL_NAME: "Gerda Henkel Foundation", 14 SEARCH_PH:"Search...", FILTER_PROF_ALL:"All professions", FILTER_NAT_ALL:"All nationalities", 15 NEW_NODES_TITLE: "New members", 16 MEMBERS_TITLE:"All members (A-Z by surname)", LOADING:"Loading networkâ¦", LAST_UPDATE:"Last update", HELP_TEXT_1: "Click a node (an individual) or edge (a connection between two individuals) to view the data they contain.", HELP_TEXT_2: "(It might take a few seconds for the website to show the network)", SITE_TITLE: "Goya Network", SITE_SUBTITLE: "Mapping Goya's Impact in 19th-Century France and Spain", 17 BTN_SEARCH: "Search", 18 BTN_CLUSTER_TOGGLE_ON: "Cluster edges: On", 19 BTN_CLUSTER_TOGGLE_OFF: "Cluster edges: Off", 20 SELECT_CLUSTER_OPTION: "Select cluster...", 21 22 CONTRIBUTIONS_TITLE: "Contributions", 23 CONTRIBUTIONS_TEXT: ` 24 <p>The Goya Network is conceived as a collaborative, crowd-sourced database.</p> 25 <p>Contributions and suggestions are welcome and encouraged, and contributors will be credited if requested.</p> 26 `, 27 28 PARTNERS_TITLE: "Partners", 29 30 RIGHTS_LABEL: "Rights:", 31 NOTARIAL_PROTECTION: "Protected by notarial deed (Madrid, 2025)", 32 ACCESS_FORM: "Access form", 33 34 CITE_TITLE: "How to cite The Goya Network", 35 BIB_TITLE: "Bibliography", BIB_INTRO: "This section lists the bibliographic sources used throughout the project. References in nodes and edges appear as short citations (e.g. Adhémar 1958).", 36 37 //professions 38 "Actor":"Actor","Archaeologist":"Archaeologist","Anthropologist":"Anthropologist","Architect":"Architect", "Aristocrat":"Aristocrat","Art critic":"Art critic", "Art dealer":"Art dealer","Art patron":"Art patron","Art historian":"Art historian","Art theorist":"Art theorist","Artist":"Artist","Banker":"Banker","Bibliographer":"Bibliographer","Bibliophile":"Bibliophile","Businessman":"Businessman","Caricaturist":"Caricaturist","Ceramist":"Ceramist","Civil servant":"Civil servant","Clergyman":"Clergyman","Collector":"Collector","Composer":"Composer","Critic":"Critic", "Curator":"Curator", "Designer":"Designer","Diplomat":"Diplomat","Draughtsman":"Draughtsman","Economist":"Econimist","Editor":"Editor", "Engineer":"Engineer", "Engraver":"Engraver","Essayist":"Essaysit","Femme de lettres":"Femme de lettres","Hispanist":"Hispanist", "Historian":"Historian","Illustrator":"Illustrator", "Journalist":"Journalist","Jurist":"Jurist","Lawyer":"Lawyer","Librarian":"Librarian","Librettist":"Librettist","Lithographer":"Lithographer","Lithographic printer":"Lithographic printer","Medical doctor":"Medical doctor","Military member":"Military member","Musician":"Musician","Novelist":"Novelist","Painter":"Painter","Philanthropist":"Philanthropist","Philosopher":"Philosopher","Print editor":"Print editor","Print dealer":"Print dealer","Photographer":"Photographer","Pianist":"Pianist","Playwright":"Playwright","Poet":"Poet","Politician":"Politician", "Printer":"Printer", "Real estate agent":"Real estate agent","Salonnière":"Salonnière","Scholar":"Scholar","Scientist":"Scientist","Sculptor":"Sculptor","Secret agent":"Secret agent","Singer":"Singer","Socialite":"Socialite","Teacher":"Teacher","Translator":"Translator","Traveller":"Traveller", "Writer":"Writer", 39 40 //nationalities 41 "Spanish":"Spanish","French":"French","British":"British","English":"English","Scottish":"Scottish","Irish":"Irish","Italian":"Italian","German":"German","Swiss":"Swiss","Belgian":"Belgian","Dutch":"Dutch","Austrian":"Austrian","Portuguese":"Portuguese","American":"American", "Cuban":"Cuban", "Polish":"Polish", "German / Czech":"German / Czech", 42 43 "Life dates":"Life dates","Sex":"Sex","male":"male","female":"female","Full name":"Full name","Also known as":"Also known as","Other names":"Other names","Pseudonyms":"Pseudonyms", "Profession":"Profession","Nationality":"Nationality","Born in":"Born in","Dies in":"Dies in","Considered as":"Considered as", "Political views":"Political views","Military activity":"Military activity","Participates in":"Participates in", "Aristocratic titles":"Aristocratic titles","Ties with slavery/colonialism":"Ties with slavery/colonialism","Curiosities":"Curiosities", "Parents":"Parents","Siblings":"Siblings","Married to":"Married to","Partners/lovers":"Partners/lovers","Children":"Children", "Friends":"Friends","Rivals":"Rivals","Correspondence":"Correspondence","Meets":"Meets","Studies in":"Studies in", "Works as":"Works as","Works for":"Works for","Influenced by":"Influenced by","Follower of":"Follower of","Masters":"Masters","Students":"Students", "Member of":"Member of","Founder of":"Founder of","Editor of":"Editor of","Writes in":"Writes in","Collaborates with":"Collaborates with", "Registered in":"Registered in","Mentioned in the French press":"Mentioned in the French press","Decorations/awards":"Decorations/awards", "Tertulia":"Tertulia","Literary salon or tertulia":"Literary salon or tertulia","Author of":"Author of","Collection":"Collection", "Patronage":"Patronage","Portraits":"Portraits","Exhibitions":"Exhibitions","Bibliography":"Bibliography","Websites":"Websites", "Podcasts":"Podcasts","Newspaper archive":"Newspaper archive","Archives":"Archives","Added":"Added","Last modified":"Last modified", "Identity and Personal Data":"Identity and Personal Data", "Family and Personal Relationships":"Family and Personal Relationships", "Education, Profession and Institutions":"Education, Profession and Institutions", "Artistic/Literary Production and Collection":"Artistic/Literary Production and Collection", "Exhibitions and Public Reception":"Exhibitions and Public Reception", "Relationship with Goya":"Relationship with Goya", "Copies after Spanish artworks":"Copies after Spanish artworks", "Sources and Documentation":"Sources and Documentation", "Connections":"Connections", "Artistic Activity, Art Collecting and Patronage":"Artistic Activity, Art Collecting and Patronage", "Geographic Presence and Movement":"Geographic Presence and Movement", "Relationship to Goya and His Work":"Relationship to Goya and His Work", 44 "Portrayed by":"Portrayed by", "Image source":"Image source", "In Spain":"In Spain", "Knows Goya's works":"Knows Goya's works", "Address":"Address", "Link to Goya's work":"Link to Goya's work", "Interest in Spanish art":"Interest in Spanish art", "Mentions of Goya":"Mentions of Goya", "Collector of Goya's works":"Collector of Goya's works", "In Paris":"In Paris", "mentions": "Mentions", "Conferences/seminars":"Conferences/seminars", "Trips":"Trips", "Lives in":"Lives in", "Patrons": "Patrons", "Collector of":"Collector of", "Copies after Goya":"Copies after Goya", "Writes about Goya":"Writes about Goya", "Mentions of Goya":"Mentions of Goya", "Connection": "Connection", "Type of connection": "Type of connection", "Type of relationship": "Type of relationship", "Know each other since": "Know each other since", "Search": "Search", "Collaborations": "Collaborations", "They met": "They met", "direct": "direct", "Artworks bought/sold": "Artworks bought/sold", "Interactions": "Interactions", "Employment/patronage": "Employment/patronage", "Between": "Between", "and": "and", "Between [X] and [Y]": "Between [X] and [Y]", "In France": "In France", "In Italy": "In Italy", "direct": "direct", "secondary": "secondary", "portraitist-sitter": "portraitist-sitter", "employer-employee": "employer-employee", "family": "family", "friends": "friends", "acquaintances": "acquaintances", "acquaintances?": "acquaintances?", "direct?": "direct?", "secondary?": "secondary?", "master-student": "master-student", "partners/lovers": "lovers", "artworks influenced by Goya": "influenced by Goya", "knows Goya's works": "knows Goya's works", "Sales": "Sales", "colleagues": "colleagues", "copies": "copies", "Nodes": "Nodes", "Connections": "Connections", "Access form": "Access form", "Unknown": "unknown", "Visits the Prado Museum": "Visits the Prado Museum", "Views on art and cultural heritage":"Views on art and cultural heritage", 45 46 ARTWORKS_TITLE: "Goya's work", 47 ARTWORKS_BODY: ` 48 <p> 49 <strong>The issue of attribution in Goya:</strong><br> 50 When studying Goya, it is important to bear in mind that, due to the enormous demand for his work since the artist's death (which far exceeds the supply of authentic pieces), countless copies, imitations and pastiches have been produced, particularly in 19th-century Spain and France. Many of these continue to be exhibited as genuine 'goyas' (more euphemistically, 'attributed to Goya', or even 'Goya's workshop') in museums around the world, driven by economic interests and s
50ocial status. New 'goyas' appear every year in art galleries, dealers, and private collections. 51 </p> 52 53 <p> 54 <strong>Catalogues raisonnés, key exhibition catalogues, and biography</strong> (see the Bibliography section):<br> 55 <ul> 56 <li><strong>Full oeuvre:</strong> Juliet Wilson-Bareau & Pierre Gassier. <em>Vie et Åuvre de Francisco Goya. L'Åuvre complet illustré. Peintures, dessins, gravure</em>, 1970 (French); <em>The Life and Complete Work of Francisco Goya: With a Catalogue Raisonné of the Paintings, Drawings and Engravings</em>, 1971 (English); <em>Vida y obra de Francisco Goya: reproducción de su obra completa, pinturas, dibujos y grabados</em>, 1974 (Spanish). See also the catalogue of the retrospective exhibition <i>Goya</i>, Martin Schwander (ed.), Fondation Beyeler, 2021 (English/Spanish/German).</li> 57 <li><strong>Drawings:</strong> Manuela Mena-Marqués & José Manuel Matilla, <em>Catálogo Razonado de los Dibujos de Francisco de Goya, vol. 2 (1771-1792)</em>, with G. Maurer, G. Solache, J. Carrete, J. Wilson & V. Albarrán, 2018 (Spanish); <em>Goya: dibujos. 'Solo la voluntad me sobra'</em>, exh. cat., 2019 (Spanish/English); and Pierre Gassier, <em>Les dessins de Goya. Les albums</em>, 1973 (French), <em>Francisco Goya. Drawings. The Complete Albums</em>, 1973 (English).</li> 58 <li><strong>Prints:</strong> Tomas Harris, <em>Goya. Engravings and Lithographs</em>, 1964. Also Juan Carrete Parrondo & Jesusa Vega, <em>Goya grabador</em>, 1991.</li> 59 <li><strong>Biography</strong>: Janis Tomlinson, <em>Goya: A Portrait of the Artist</em>, 2020 Princeton UP (English), 2022 Cátedra (Spanish).</li> 60 </ul> 61 </p> 62 63 <p> 64 <strong>Online resources:</strong><br> 65 <ul> 66 <li><strong>Wikipedia:</strong> a comprehensive, crowd-sourced overview of Goya's works displayed in a table, showing title, image, date, location and attribution. <a href="https://en.wikipedia.org/wiki/List_of_works_by_Francisco_Goya" target="_blank"> List of Works by Francisco de Goya</a> (English) - Paintings and Prints; <a href="https://es.wikipedia.org/wiki/Anexo:Cuadros_de_Goya" target="_blank"> Cuadros de Goya</a> (Spanish) - Paintings (non exhaustive).</li><br> 67 <li><a href="https://fundaciongoyaenaragon.es/" target="_blank"><strong>Fundación Goya en Aragón:</strong></a> an online catalogue of Goya's works collated by the Fundación Goya en Aragón (part of the Government of Aragón), that includes descriptions, provenance, bibliography, and images. This catalogue, however, collects <em>all</em> works ever attributed to Goya, including copies and pastiches.</li><br> 68 </ul> 69 </p> 70 71 <p> 72 <strong>GOYA COLLECTIONS: museums, libraries, churches, and private collections:</strong><br> 73 <br> 74 SPAIN 75 <li><a href="https://www.museodelprado.es/coleccion/artista/goya-y-lucientes-francisco-de/39568a17-81b5-4d6f-84fa-12db60780812" target="_blank"><strong>Museo del Prado</strong></a> (<em>Goya en el Prado</em>): artworks (paintings, prints and drawings) held at the Prado Museum, documents, printed references, and digital library.</li> 76 <li><a href="https://www.realacademiabellasartessanfernando.com/goya/la-academia-y-goya/" target="_blank"><strong>Real Academia de Bellas Artes de San Fernando</strong></a> (RABASF): history of Goya's link with the Academy, catalogue of works, <em>CalcografÃa Nacional</em> (production of Goya's prints).</li> 77 <li><strong>Patrimonio Nacional</strong> (Spanish national heritage): different paintings such as <a href="https://www.patrimonionacional.es/colecciones-reales/pintura/retrato-de-carlos-iv-como-cazador" target="_blank"><em>Portrait of Carlos IV as a Hunter</em></a> and <a href="https://www.galeriadelascoleccionesreales.es/obra-de-arte/retrato-de-maria-luisa-de-parma-en-traje-de-corte/19602330-5985-4e7e-81f7-25dce124b6a9" target="_blank"><em>Portrait of MarÃa Luisa de Parma in Court Dress</em></a>, as well as all the tapestries based on Goya's cartoons, such as <a href="https://www.patrimonionacional.es/colecciones-reales/textiles-y-tapiceria/la-era-o-el-verano" target="_blank"><em>The Summer</em></a>.</li> 78 <li><a href="https://www.bne.es/es/blog/blog-bne/post-80" target="_blank"><strong>Biblioteca Nacional de España</strong></a> (BNE): history of Goya's collections and catalogue.</li> 79 <li><a href="https://coleccion.bde.es/wca/es/secciones/coleccion/artistas/francisco-de-goya-y-lucientes-896.obras.html" target="_blank"><strong>Banco de España</strong></a>
79 (Madrid): paintings (such as the portraits of Francisco Cabarrús, Count of Floridablanca, XI Count of Altamira and Carlos III) and prints (<em>Desastres de la guerra</em>, <em>Tauromaquia</em>, <em>Disparates</em>).</li> 80 <li><a href="https://www.museolazarogaldiano.es/obras-goya-museo-lazaro-galdiano" target="_blank"><strong>Museo Lázaro Galdiano</strong></a> (Madrid): its collection includes the paintings <i>El Aquelarre</i> (1797-98) and <i>Las Brujas/El conjuro</i> (1797-98), the oil sketches <i>Santa Isabel de Portugal curando a una enferma</i> (c.1799) and <i>San Hermenegildo en la prisión</i> (1800), and the preparatory sketch <i>El verano</i> (1786); prints like <i>El can</i> (c.1820-27); and drawings like <i>Felipe III</i> (1778), <i>Retrato ecuestre del Conde-Duque de Olivares</i> (c.1776-78), <i>Portrait of Giambattista Casti</i> (c.1802-03), <i>Modo de volar</i> (1815-16), <i>Mendigo ciego con un perro</i> (c.1824) and <i>Mendigo con acordeón</i> (c.1824-28).</li> 81 <li><a href="https://www.museothyssen.org/coleccion/artistas/goya-francisco" target="_blank"><strong>Museo Thyssen Bornemisza</strong></a> (Madrid): biography and collection, which includes the paintings <i>Retrato de Asensio Julià </i> (c.1798), <i>Retrato de Fernando VII</i> (1814-15), and <i>El TÃo Paquete</i> (c.1819-20).</li> 82 <li><a href="https://museogoya.fundacionibercaja.es/goya-en-zaragoza.php" target="_blank"><strong>Museo Goya - Colección Fundación Ibercaja - Museo Camón Aznar</strong></a> (Zaragoza).</li> 83 <li><a href="https://fundacionfuendetodosgoya.org/museo-del-grabado/" target="_blank"><strong>Museo del Grabado</strong></a> (Fuendetodos, Zaragoza): museum of printmaking at Goya's birth house.</li> 84 <li><strong>CHURCHES AND CATHEDRALS</strong>: Ermita de San Antonio de la Florida (Madrid), Real BasÃlica de San Francisco el Grande (Madrid), Valencia Cathedral, Sevilla Cathedral, BasÃlica del Pilar (Zaragoza), Cartuja del Aula Dei (Zaragoza).</li> 85 <li><strong>PRIVATE COLLECTIONS</strong> (selection): colección Abelló; colección Marqués de la Romana; Duke of Alba (Palacio de Liria); Dukes of Osuna (Alameda de Osuna); Carderera collection; Fundación MarÃa Cristina Masaveu Peterson; Fundación BBVA (Madrid, Palacio del Marqués de Salamanca): <a href="https://www.coleccionbbva.com/es/pintura/443-retrato-de-carlos-iii-cazador/" target="_blank"><em>Carlos III as a Hunter</em></a> and <a href="https://www.coleccionbbva.com/es/pintura/444-don-pantaleon-perez-de-nenin/" target="_blank"><em>Portrait of Don Pantaleón Pérez de NenÃn</em></a>.</li><br> 86 87 FRANCE<br> 88 <li><a href="https://collections.louvre.fr/en/recherche?q=Francisco%20de%20Goya" target="_blank"><strong>Musée du Louvre</strong>:</a> paintings, prints, and drawings.</li> 89 <li><a href="https://www.museegoya.fr/fr/search?p=1&query=Goya" target="_blank"><strong>Musée Goya Castres</strong>:</a> paintings, prints, copies after Goya, and documents. Its collection includes <a href="https://www.museegoya.fr/fr/notice/894-5-4-l-assemblee-de-la-compagnie-royale-des-philippines-dite-la-junte-des-philippines-7dc13026-e99a-4d4d-a924-afa2204705e9" target="_blank"><i>La Junta de Filipinas</i></a> (L'Assemblée de la Compagnie Royale des Philippines) (1815) (894-5-4), the <a href="https://www.museegoya.fr/fr/notice/894-5-3-portrait-de-francisco-del-mazo-a3f16ab8-79d5-46ea-b3e8-5fb553468f2c" target="_blank"><i>Portrait of Francisco del Mazo</i></a> (1815-19) (894-5-3), and a <a href="https://www.museegoya.fr/fr/notice/894-5-2-autoportrait-aux-lunettes-36227304-cf57-402d-a357-5c5647a9aa7c" target="_blank"><i>Self-portrait with Glasses</i></a> (c.1800) (894-5-2).</li> 90 <li><a href="https://gallica.bnf.fr/selections/fr/html/francisco-de-goya" target="_blank"><strong>Bibliothèque nationale de France</strong></a> (BnF): history of the Goya collections, and catalogue (prints).</li> 91 <li>Palais des Beaux-arts de Lille: <a href="https://pba.lille.fr/Collections/Chefs-d-OEuvre/Peintures-XVI-sup-e-sup-XXI-sup-e-sup-siecles/Les-Vieilles-Le-Temps" target="_blank"><em>Les vieilles/Le temps</em></a> and <a href="https://pba.lille.fr/Collections/Chefs-d-OEuvre/Peintures-XVI-sup-e-sup-XXI-sup-e-sup-siecles/Les-Jeunes-La-Lettre" target="_blank"><em>Les jeunes/La lettre</em></a>. Also a set of the <em>Caprichos</em>.</li> 92 <li><a href="https://www.mbaa.besancon.fr/les-collections/peinture/" target="_blank"><strong>Musée des Beaux-Arts de Besançon</strong></a>: <em>Cannibals watching human remains</em> and <em>Cannibals preparing their victims</em>
92.</li> 93 <li>Musée des Beaux-Arts de Strasbourg: <a href="https://musees-strasbourg.skin-web.org/document/portrait-de-don-bernardo-iriarte/5ee338d7461cda28a3ab1e90?q=Goya&pos=14&pgn=0" target="_blank"><em>Portrait de Don Bernardo de Iriarte</em></a>, as well as prints (after Velázquez, <em>Caprichos</em>, <em>Disparates</em>, isolated prints like <em>El agarrotado</em>).</li> 94 <li><a href="https://www.musee-agen.fr/recherche?q=Goya&tx_indexedsearch%5Bsubmit_button%5D=OK" target="_blank"><strong>Musée des Beaux-Arts d'Agen</strong></a>: some paintings attributed to Goya, such as a self-portrait and <em>Le Ballon</em>.</li> 95 <li><a href="https://mbh.bayonne.fr/" target="_blank"><strong>Musée Bonnat-Helleu</strong></a> (Bayonne): works such as the drawing <em>Dos mujeres abrazándose</em> and portrait of Goya with glasses (attributed).</li> 96 <li>PRIVATE COLLECTIONS: Paris collection (the twin portraits of Javier Goya and Gumersinda Goicoechea).</li><br> 97 98 USA<br> 99 <li><a href="https://collections.mfa.org/search/Objects/*/Goya/images?page=1" target="_blank"><strong>Boston Museum of Fine Arts</strong></a> (MoFA): paintings (<a href="https://www.metmuseum.org/art/collection/search?q=Goya&sortBy=Relevance&searchField=ArtistCulture" target="_blank"><em>Portrait of a Young Man in Brown, possibly Javier Goya</em></a>, 48.558), <a href="https://collections.mfa.org/collections/315210/dpdprints-by-goya/objects" target="_blank">prints</a> and <a href="https://collections.mfa.org/collections/315215/dpddrawings-by-goya;jsessionid=7527C5D8C33C9415AF5A65EC964E77C1/objects" target="_blank">drawings</a>.</li> 100 <li><a href="https://www.metmuseum.org/art/collection/search?q=Goya&sortBy=Relevance&searchField=ArtistCulture" target="_blank"><strong>Metropolitan Museum</strong></a> (New York): paintings, prints, and drawings.</li> 101 <li><a href="https://www.nortonsimon.org/art/in-focus/goya" target="_blank"><strong>Norton Simon Museum</strong></a> (Pasadena): history of the Goya collections, and catalogue.</li> 102 <li><a href="https://www.frick.org/art/artists/goya" target="_blank"><strong>The Frick Collection</strong></a> (New York): history of the Goya collections, and catalogue.</li> 103 <li><a href="https://hispanicsociety.emuseum.com/search/Goya*" target="_blank"><strong>The Hispanic Society of America</strong></a> (New York): Goya collection, including paintings (<i>Portrait of the Duchess of Alba in black</i>, 1797, A102; <i>Portrait of Pedro Mocarte</i>, c.1805, A1890; <i>Portrait of Don Manuel Lapeña</i>, 1799, A99), prints (<i>Los Caprichos</i>), </i>and drawings (<i>Majas fighting, a Mano observing</i>, 1796-07, A3309; <i>Majas y majos conversing</i>, 1796-97, A3310; <i>Hay pulgas?</i>, 1796-97, A3316; <i>Buena gente somos los moralistas</i>, 1796-97, A3317; <i>Juan Fernández Navarrete</i>, 1798, LA2223; <i>Tiene prisa de escapar</i>, 1808-14, A3318; <i>Torture of a man</i>, c.1812-20, A3312; <i>Peasant carrying woman</i>, 1812-20, A3315; <i>Tuti li mundi</i>, c.1808-14, A3314; <i>Regocijo</i>, 1819-23, A3308; <i>Old man on a swing</i>, 1823-28, A3313; <i>Woman with 2 children</i>, 1823-28, A3311).</li> 104 <li><a href="https://www.nga.gov/artists/1353-francisco-goya" target="_blank"><strong>National Gallery of Art</strong></a> (Washington, D.C.): paintings, prints, and drawings.</li> 105 <li><a href="https://meadowsmuseumdallas.org/collections/pages/objects-1/portfolio/?records=9&query=mfs%20any%20%22Goya%22&sort=0" target="_blank"><strong>The Meadows Museum</strong></a> (Dallas): paintings (including copies and pastiches).</li> 106 <li><a href="https://www.artic.edu/artists/40615/francisco-jose-de-goya-y-lucientes" target="_blank"><strong>Art Institute of Chicago</strong></a>: paintings, prints, and drawings (including pastiches).</li> 107 <li><a href="https://harvardartmuseums.org/collections/person/23169?person=23169" target="_blank"><strong>Harvard Art Museums</strong></a> (Cambridge, Mass.): paintings, prints and drawings (including pastiches).</li> 108 <li><strong>Museo de Arte de Ponce</strong> (Puerto Rico): <a href="https://museoarteponce.org/obras/retrato-de-martin-zapater-portrait-of-martin-zapater/" target="_blank"><em>Portrait of MartÃn Zapater</em></a>.</li><br> 109 110 UK<br> 111 <li><a href="https://www.nationalgallery.org.uk/artists/francisco-de-goya" target="_blank"><strong>National Gallery</strong></a> (London): <a href="https://www.nationalgallery.org.uk/paintings/francisco-de-goya-a-picnic" target="_blank"><em>A Picnic</em></a>, <a href="https://www.nationalgallery.org.uk/paintings/francisco-de-goya-a-scene-from-the-forcibly-bewitched" target="_blank"><em>A Scene from 'The Forcibly Bewitched'</em></a>, <a href="https://www.nationalgallery.org.uk/paintings/francisco-de-goya-don-andres-del-peral" target="_blank"><em>Don Andrés del Peral</em></a>, and <a href="https://www.nationalgallery.org.uk/paintings/francisco-de-goya-the-duke-of-wellington" target="_blank"><em>The Duke of Wellington</em></a>. Also <a href="https://www.nationalgallery.org.uk/paintings/francisco-de-goya-dona-isabel-de-porcel" target="_blank"><em>Portrait of Isabel de Porcel</em></a> (attributed).</li> 112 <li><a href="https://thebowesmuseum.org.uk/discover-the-spanish-collection-held-by-the-bowes-museum/" target="_blank"><strong>The Bowes Museum</strong></a> (Durham): paintings including the portrait of Juan Meléndez Valdé
112s and <i>Interior of a Prison</i>.</li> 113 <li><strong>English Heritage</strong>: The Wellington Collection, Apsley House (London) (<a href="https://artuk.org/discover/artworks/equestrian-portrait-of-the-1st-duke-of-wellington-17691852-144183" target="_blank">Equestrian Portrait of the 1st Duke of Wellington</a>).</li> 114 <li><a href="https://artuk.org/discover/artworks/el-medico-the-doctor-210121" target="_blank"><strong>National Galleries of Scotland</strong></a> (Edinburgh): <em>The Doctor</em>.</li> 115 <li><strong>The Courtauld Gallery</strong> (London): the drawing <a href="https://courtauld.ac.uk/highlights/cantar-y-bailar-singing-and-dancing/" target="_blank"><em>Singing and Dancing</em></a> and the painting <a href="https://courtauld.ac.uk/highlights/portrait-of-francisco-de-saavedra/" target="_blank"><em>Portrait of Francisco de Saavedra</em></a>.</li> 116 <li><strong>Pollok House</strong> (Glasgow): series of small 6 paintings of scenes with children, including <a href="https://artuk.org/discover/artworks/boys-playing-at-see-saw-84159" target="_blank"><em>Boys Playing at See-Saw</em></a> and <a href="https://artuk.org/discover/artworks/boys-playing-at-soldiers-84160" target="_blank"><em>Boys Playing at Soldiers</em></a>.</li> 117 <li><a href="https://artuk.org/discover/artworks/disparate-de-miedo-261946" target="_blank"><strong>The Higgins</strong></a> (Bedford): 'Disparate de miedo' (<em>Disparates</em>).</li><br> 118 119 IRELAND<br> 120 <li><strong>National Gallery of Ireland</strong> (Dublin): <a href="https://www.nationalgallery.ie/art-and-artists/highlights-collection/dona-antonia-zarate-francisco-jose-de-goya-y-lucientes-1746" target="_blank"><em>Portrait of Doña Antonia de Zárate</em></a>.</li><br> 121 122 SWITZERLAND<br> 123 <li><strong>Emil Bührle Collection</strong> (Zurich): <a href="https://buehrle.ch/en/artworks/procession-in-valencia/" target="_blank"><em>Procession in Valencia</em></a>.</li> 124 <li>PRIVATE COLLECTIONS: Rothschild (including the authentic version of <em>Majas on the Balcony</em>).</li><br> 125 126 GERMANY<br> 127 <li><a href="https://recherche.smb.museum/?language=de&limit=15&sort=relevance&controls=none&conditions=AND%2BinvolvedParties%2B%22Francisco+Jos%C3%A9+de+Goya+y+Lucientes%22&collectionKey=NG*" target="_blank"><strong>Staatliche Museen zu Berlin</strong></a>: catalogue of artworks, including <em>La Cucaña</em>.</li> 128 <li><a href="https://www.sammlung.pinakothek.de/de/search?phrase=Goya#filters={}" target="_blank"><strong>Alte Pinakothek</strong></a> (Munich): paintings, prints, and drawings (including pastiches).</li><br> 129 130 ITALY<br> 131 <li><strong>Uffizi</strong> (Florence): <a href="https://www.virtualuffizi.com/es/retrato-de-la-condesa-de-chinc%C3%B3n.html" target="_blank"><em>Equestrian portrait of MarÃa Teresa de Vallabriga</em></a>.</li> 132 <li><strong>Fondazione Magnani Rocca</strong> (Parma): <a href="https://www.magnanirocca.it/goya-la-famiglia-dell-infante-don-luis/" target="_blank"><em>The Family of Infante Don Luis</em></a>.</li><br> 133 134 </p> 135 `, 136 137 PEOPLE_TITLE: "People", 138 PEOPLE_BODY: ` 139 <p><strong>Project Director:</strong> Paula Fayos-Pérez, postdoctoral researcher. PhD in History of Art (University of Cambridge, 2019). Associate member of ARCHE (Université de Strasbourg) and SU+MA (Universidad Complutense de Madrid).</p> 140 141 <p><strong>Scientific Committee:</strong> The content of the Goya Network is periodically reviewed and discussed with the members of a scientific committee composed of external consultants renowned for their scholarly prestige, who provide guidance and advice on specific aspects of the project.</p> 142 <ul style="margin-top: 0.5rem; line-height: 1.6;"> 143 <li><strong>Jean Michel Massing</strong>: Professor Emeritus of Art History at the University of Cambridge, King's College. Specialist in the history of printmaking, iconography, and African art.</li> <br> 144 <li><strong>Aurélie Pétiot</strong>: Lecturer at the Université Paris Nanterre. Specialist in nineteenth-century art, decorative arts, gender studies, and the Pre-Raphaelites.</li> <br> 145 <li><strong>Stephanie Loeb Stepanek</strong>: Curator Emerita of Prints and Drawings at the Museum of Fine Arts, Boston. Specialist in Goya's prints and the history of Goya collecting in the United States.</li> <br> 146 <li><strong>Anita L. VerÅ</strong>: PhD in Artificial Intelligence (University of Cambridge, 2022), computer scientist and founder of <a href="https://www.consonant-ai.com" target="_blank">ConsonantAI</a>. Support with the technical development of the website.</li> 147 </ul> 148 149 <p class="section-heading">Contributors</p> 150 <ul> 151 <li><strong>Manuela B. Mena-Marqués</strong>: PhD in Art History (Complutense University of Madrid, 1976). Curator of the Prints and Drawings Department (1979-1981), Deputy Director (1981-1996) and Head of the Curatorial Department for 18th-Century Painting and Goya at the Prado Museum (1996-2019). Contribution: feedback on the website (June 2025).</li> <br> 152 <li><strong>Joëlle Arches</strong>, Chief Curator and Director of the <a href="https://www.museegoya.fr/" target="_blank">Musée Goya</a> and Jean Jaurès Museum, and <strong>Cécile Berthoumieu</strong>, Senior Heritage Curator (Attachée principale de conservation du patrimoine). Contribution: website feedback; suggested adding Marcel Briguiboul to the network (September 2025).</li> <br> 153 <li><strong>Stephanie Loeb Stepanek</strong>: Curator Emerita of Prints and Drawings, Boston Museum of Fine Arts. Contribution: information on an early edition of <i>Disasters of War</i>, printed by Laureano Potenciano (October 2025).</li> 154 </ul> 155 `, 156 157 ABOUT_TITLE: "About the Goya Network", 158 ABOUT_BODY: ` 159 160 <p><strong>Introduction:</strong> Throughout the 18th and 19th centuries, networks of artists, writers, collectors and intellectuals were crucial for artistic production, as well as for the dissemination and understanding of historical figures. Mapping these connections allows us to better understand the dynamics of sociability and the circulation of ideas. In recent years, network analysis has become an important tool in Digital Humanities for illuminating historical artistic milieus (see the <a href="#" onclick="showSection('relevant-links'); return false;">Relevant links</a> section for examples of similar projects).</p> 161 162 <p><em>The Goya Network</em> builds on these methods, offering an interactive database that maps the social and professional relationships surrounding the Spanish artist Francisco de Goya, focusing on his connections with artists, critics, and collectors in 19th-century Spain and France. Unlike traditional studies of his work, this project emphasizes the relational context that shaped Goya's legacy, offering a novel tool for the study of historical artists' social networks. As a 'living' database, it facilitates cross-disciplinary collaboration and serves as both a research tool and a public resource, providing insight into the scope of Goya's influence and the reception of his work.</p> 163 164 <p><strong>Origin:</strong> This is a research-based digital project developed by Paula Fayos-Pérez as part of her postdoctoral work on Goya's social and professional networks. It is a continuation of the research done during her PhD (University of Cambridge, 2018) <a href="https://www.repository.cam.ac.u
164k/items/8d9751ce-aca1-4d35-9436-0820ecbc98d4" target="_blank">(repository)</a>, which was later transformed into a book: <a href="https://www.ceeh.es/en/publicacion/goyas-caprichos-in-nineteenth-century-france-politics-of-the-grotesque/" target="_blank"><em>Goya's Caprichos in Nineteenth-Century France. Politics of the Grotesque</em></a> (CEEH, 2024).</p> 165 166 <p><strong>Funding:</strong> This project was initiated with the support of a 2024 Leonardo Grant (<a href="https://www.redleonardo.es/beneficiario/paula-fayos-perez/" target="_blank">Beca Leonardo</a>) for Scientific Research and Cultural Creation, funded by <a href="https://www.fbbva.es/" target="_blank">BBVA Foundation.</a> BBVA Foundation takes no responsibility for the opinions, statements and contents of this project, which are entirely the responsibility of its authors.</p> 167 168 <p>The project is currently supported by a research grant from <a href="https://www.gerda-henkel-stiftung.de/" target="_blank">Gerda Henkel Stiftung (Gerda Henkel Foundation).</a></p> 169 170 <p><strong>User guide:</strong> There is data contained in each node (individual) and edge (connection between two individuals). On the nodes, users can see biographic information as well as artistic/literary production, art collection and link to Goya's work. On the edges, one can see the relationship type, correspondence, collaborations, and others. Edges in grey/silver colour are direct connections (friends, colleagues, relatives, acquaintances, etc.), while edges in golden colour are secondary connections (e.g. artists and writers who copied or wrote about Goya but never met him in person). The size of the nodes is proportional to the number of edges (connections) they hold.</p> 171 172 <p><strong>Nature of the project:</strong> The Goya Network is conceived as a collaborative, crowd-sourced database with the aim of becoming a useful tool for scholars, students and the general public of Goya. We welcome contributions by art historians, historians, Hispanists, lecturers and curators specialised in Goya, 19th-century Spain, 19th-century France and in any of the members of the network (e.g. Federico Madrazo, Prosper Mérimée, Eugène Delacroix, etc.). To submit a contribution, please see the Contributions section on the website below.</p> 173 174 <p><strong>Methodology:</strong> <u>Art historical research</u>: based on systematic analysis of bibliographic and archival sources (such as correspondence) from 19th-century France and Spain. Network nodes and edges were constructed through careful cross-referencing of academic publications (monographs, peer-reviewed articles, exhibition catalogues), institutional websites and online collection databases of museums and cultural foundations, and verified digital resources.<br> 175 <u>Web development</u>: built using HTML and JavaScript, with structured data in JSON, and version control and deployment through GitHub, developed with the assistance of ChatGPT (OpenAI), DeepSeek (High-Flyer), and Claude (Anthropic). Initial website setup in collaboration with Anita VerŠ<a href="https://www.consonant-ai.com" target="_blank">(ConsonantAI)</a>.</p> 176 177 <p><strong>Copyright and License:</strong> The Goya Network is protected by notarial deed (Madrid, 2025), documenting the research and development process from 2018 to 2025. 178 Its code and structure are licensed under <a href="https://creativecommons.org/licenses/by-nc/4.0/" target="_blank" style="color:#66ccff;">CC BY-NC 4.0</a>, allowing non-commercial academic use with proper attribution to the author.</p> 179 180 <p><strong>Data and cookies:</strong> This website uses cookies exclusively for Google Analytics, which anonymously tracks visitor behaviour. We use this information only to understand how the site is being used and to improve its content and navigation. You can opt out of Google Analytics cookies across all websites by installing the <a href="https://tools.google.com/dlpage/gaoptout" target="_blank" style="color:#66ccff;">Google Analytics Opt-out Browser Add-on</a>
180. For more information on how Google uses cookies, visit the <a href="https://developers.google.com/analytics/devguides/collection/analyticsjs/cookie-usage" target="_blank" style="color:#66ccff;">Cookies & Google Analytics</a> page.</p> 181 `, 182 183 NEWS_TITLE: "News", 184 NEWS_BODY: ` 185 <ul style="padding-left:1.2rem; line-height:1.7;"> 186 <li> 187 <strong>July 2026.</strong> The Goya Network has been presented at the <a href="https://www.eventbrite.co.uk/e/goya-network-tickets-1991696763635?aff=oddtdtcreator" target="_blank">Instituto Cervantes London</a>, in an event chaired by Manuela B. Mena Marqués. 188 </li> 189 <li> 190 <strong>May 2026.</strong> The research project developping the Goya Network has now received funding from the <a href="https://www.gerda-henkel-stiftung.de/" target="_blank">Gerda Henkel foundation (Germany)</a>. 191 </li> 192 <li> 193 <strong>March 2026.</strong> The Goya Network continues to expand with new members and partnerships. New partnership: <a href="https://crimic-sorbonne.fr/" target="_blank">CRIMIC (Sorbonne Université)</a>. 194 </li> 195 <li> 196 <strong>September 2025.</strong> A new partnership has been established with <a href="https://artes-uk.org/" target="_blank">ARTES (Iberian & Latin American Visual Culture Group) (UK)</a>. 197 </li> 198 <li> 199 <strong>September 2024.</strong> The Goya Network obtains funding from the <a href="https://www.redleonardo.es/noticias/paula-fayos-crea-goya-network-beca-leonardo/" target="_blank">BBVA Foundation (Leonardo Grant)</a>. 200 </li> 201 </ul> 202 `, 203 204 MENTIONS_TITLE: "Mentions", 205 MENTIONS_BODY: ` 206 <h3 style="margin-top:1.5rem; margin-bottom:0.8rem;">Academic articles</h3> 207 <ul style="padding-left:1.2rem; line-height:1.7;"> 208 <li><strong><i>Leonardo</i> (MIT Press): forthcoming</strong> â Paula Fayos Pérez & Anita VerÅ, âThe Goya Network: Mapping Goya's Impact in 19th-Century France and Spainâ (in English).</li> 209 </ul> 210 211 <h3 style="margin-top:1.5rem; margin-bottom:0.8rem;">Conferences and presentations</h3> 212 <ul style="padding-left:1.2rem; line-height:1.7;"> 213 <li><strong>Instituto Cervantes London (2026)</strong> â 'Goya Network: Francisco de Goya's influence on 19th-century French art and literature' (in English) <a href="https://www.eventbrite.co.uk/e/goya-network-tickets-1991696763635?aff=oddtdtcreator" target="_blank">(see event)</a>.</li> 214 <li><strong>Prado Joven 2025 (Museo del Prado)</strong> â 'El impacto de Goya en el siglo XIX: de Gautier a Madrazo' (in Spanish) <a href="https://www.museodelprado.es/recurso/programa-joven-2025/570e6e48-01fc-48b4-b8f6-270e3b3141f3" target="_blank">(program)</a>. <a href="https://www.youtube.com/watch?v=jSGfYz08mP83" target="_blank">Watch video</a>.</li> 215 <li><strong>Congreso Humanidades Digitales Hispánicas 2025 (Universidad de Valencia)</strong> â 'La Red Goya: Humanidades Digitales para mapear la recepción de Goya en el siglo XIX' (in Spanish) <a href="https://hdh2025.uv.es/static/files/Programa_Congreso_1_8.pdf" target="_blank">(view program)</a>.</li> 216 <li><strong>Asociación Humanidades Digitales Hispánicas (2025)</strong> â 'Un café con Paula Fayos Pérez. La Red Goya: Humanidades Digitales para mapear la recepción de Goya en el siglo XIX' (in Spanish) <a href="https://humanidadesdigitaleshispanicas.es/cafe-con-paula-fayos-perez-16-de-diciembre-de-2025/" target="_blank">(see event)</a>.</li> 217 </ul> 218 219 <h3 style="margin-top:1.5rem; margin-bottom:0.8rem;">Online presence</h3> 220 <ul style="padding-left:1.2rem; line-height:1.7;"> 221 <li><strong>ARTES UK (Iberian & Latin American Visual Culture Group)</strong> â <a href="https://artes-uk.org/links-2/" target="_blank">the Goya Network included among the links</a> (in English).</li> 222 <li><strong>XIX (Société des études romantiques & dix-neuviémistes)</strong> â <a href="https://serd.hypotheses.org/19526" target="_blank">The Goya Network (Réseau Goya)</a> (in French).</li> 223 <li><strong>Daumier.de</strong> â <a href="https://www.daumier.de/2025/07/30/das-goya-network/" target="_blank">Das Goya Network</a> (in German).</li> 224 <li><strong>Red Leonardo</strong> â <a href="https://www.redleonardo.es/noticias/paula-fayos-crea-goya-network-beca-leonardo/?fbclid=IwY2xjawMuhCNleHRuA2FlbQIxMQBicmlkETFmVHVybzVkRTNnNEZ3bGExAR7q3T7lseZC1CU_pp-vGJWXCjVU8C_gUFvqiVPRd7efUEbjLWnIKRb_WU9Ieg_aem_JIlJ6CO77pnEaRFtx1uKmQ" target="_blank">'Paula Fayos crea la Goya Network [...]'</a> (in Spanish).</li> 225 </ul> 226 227 <h3 style="margin-top:1.5rem; margin-bottom:0.8rem;">Interviews</h3> 228 <ul style="padding-left:1.2rem; line-height:1.7;"> 229 <li><strong><i>Mas De Arte</i></strong> â <a href="https://masdearte.com/especiales/paula-fayos-perez/" target="_blank">Paula Fayos Pérez in Especiales/Fichados</a> (in Spanish).</li> 230 <li><strong><i>El PaÃs</i></strong> â forthcoming (in Spanish).</li> 231 </ul> 232 `, 233 234 RELEVANT_LINKS_TITLE: "Relevant links & resources", 235 RELEVANT_LINKS_BODY: ` 236 237 <h3 style="margin-top:1.5rem; margin-bottom:0.8rem;">Goya</h3> 238 <ul style="padding-left:1.2rem; line-height:1.6;"> 239 <li><a href="https://www.museodelprado.es/coleccion/artista/francisco-de-goya-y-lucientes/39568a17-81b5-4d6f-84fa-12db60780812" target="_blank"><strong>Goya en el Prado </strong></a> â A digital resource of the artist to learn about his paintings, prints and drawings in the museum, as well as documents, print references and a digital library (in Spanish).</li> 240 <li><a href="https://www.realacademiabellasartessanfernando.com/goya/la-academia-y-goya/" target="_blank"><strong>Real Academia de Bellas Artes de San Fernando: La Academia y Goya</strong></a> (in Spanish) â The History of Goya with the Royal Academy of Fine Arts in Madrid, as well as an overview of the Goya collections, particularly his print series.</li> 241 </ul> 242 243 <h3 style="margin-top:1.5rem; margin-bottom:0.8rem;">19th Century / France</h3> 244 <ul style="padding-left:1.2rem; line-height:1.6;"> 245 <li><a href="https://www.musee-delacroix.fr/fr/informations-complementaires/liens/societe-des-amis-du-musee" target="_blank"><strong>Société des Amis du musée Delacroix</strong></a> â Dedicated to the study and promotion of Eugène Delacroix's work and legacy (in French).</li> 246 <li><a href="https://prosper-merimee.com/" target="_blank"><strong>Société Mérimée</strong></a> â An organisation and academic journal (<i>Cahiers Mérimée</i>)dedicated to the figure of Prosper Mérimée (in French).</li> 247 <li><a href="https://www.societedesetudesmarcelinedesbordesvalmore.fr/" target="_blank"><strong>Société des études Marceline Desbordes-Valmore</strong></a> â Promoting the study of the 19th-century French poet and novelist (in French).</li> 248 <li><a href="https://www.theophilegautier.fr/" target="_blank"><strong>
248Société Théophile Gautier</strong></a> â An association dedicated to the life, work, and literary legacy of Théophile Gautier (in French).</li> 249 <li><a href="https://www.daumier.de/" target="_blank"><strong>Daumier.de</strong></a> â Dedicated to the study and promotion of Honoré Daumier's work and legacy (in German).</li> 250 <li><a href="https://serd.hypotheses.org/" target="_blank"><strong>XIX Société des études romantiques & dix-neuvièmistes</strong></a> â A society dedicated to the study of Romanticism and the 19th century (in French).</li> 251 <li><a href="https://19cwwfw.org/" target="_blank"><strong>Nineteenth-Century Women Writers in the Francophone World</strong></a> (by Bénédicte Monicat and Morgane Haesen) â A database of female writers in the 19th century in the francophone world (in English).</li> 252 <li><a href="https://19thc-artworldwide.org/" target="_blank"><strong>Nineteenth-Century Art Worldwide</strong></a> â A journal dedicated to the study of 19th-century art and visual culture (in English).</li> 253 </ul> 254 255 <h3 style="margin-top:1.5rem; margin-bottom:0.8rem;">General databases</h3> 256 <ul style="padding-left:1.2rem; line-height:1.6;"> 257 <li><a href="https://historia-hispanica.rah.es/" target="_blank"><strong>Real Academia de la Historia (RAH): Historia Hispánica</strong></a> â A digital portal created by the Spanish Royal Academy of History that uses a map to show the global history of Spain and the territories that were part of the Spanish administration (Spanish/English).</li> 258 <li><a href="https://www.getty.edu/" target="_blank"><strong>J. Paul Getty Trust</strong></a> â Includes the Getty Museum, Getty Research Institute, and extensive art historical databases (in English).</li> 259 <li><a href="https://www.bnf.fr/" target="_blank"><strong>Bibliothèque nationale de France (BnF)</strong></a> â France's National Library with digitized collections including letters, manuscripts, and prints (in French/English/Spanish).</li> 260 <li><a href="https://www.bne.es/" target="_blank"><strong>Biblioteca Nacional de España (BNE)</strong></a> â Spain's National Library with extensive digital resources (in Spanish).</li> 261 </ul> 262 263 <h3 style="margin-top:1.5rem; margin-bottom:0.8rem;">Other Digital Humanities (DH) Projects</h3> 264 <ul style="padding-left:1.2rem; line-height:1.6;"> 265 <li><a href="https://arkivet.thorvaldsensmuseum.dk/en" target="_blank"><strong>Thorvaldsen Letter Archive</strong></a> â Mapping the correspondence and social networks of the neoclassical sculptor Bertel Thorvaldsen (English/Dansk).</li> 266 <li><a href="http://republicofletters.stanford.edu/casestudies/index.html" target="_blank"><strong>Mapping the Republic of Letters</strong></a> (University of Stanford) â An interactive visualization of intellectual networks in early modern Europe (in English).</li> 267 <li><a href="http://emlo.bodleian.ox.ac.uk/home" target="_blank"><strong>Early Modern Letters Online (EMLO)</strong></a> (University of Oxford) â A catalog and analysis of correspondence from the early modern period (in English).</li> 268 <li><a href="https://mappingrome.com/" target="_blank"><strong>Mapping Rome</strong></a> â Visualizing social, economic, and cultural networks in ancient Rome (in English).</li> 269 <li><a href="https://vangoghworldwide.org/" target="_blank"><strong>Van Gogh Worldwide</strong></a> â A collaborative platform connecting Van Gogh's works, letters, and cultural impact across institutions worldwide (in English).</li> 270 <li><a href="https://womensbios.lib.virginia.edu/index.html" target="_blank"><strong>Collective Biographies of Women</strong></a> (Alison Booth, University of Virginia) â Aggregating biographical data on women across multiple disciplines and time periods (in English).</li> 271 </ul> 272 `, 273 274 CONTACT_TITLE: "Contact", 275 CONTACT_BODY: ` 276 <p>For collaborations, suggestions, or contributions:</p> 277 278 <p style="margin-top:0.5rem;"> 279 <a href="mailto:[email protected]" style="font-weight:bold;"> 280 [email protected] 281 </a> 282 </p> 283 284 <p style="margin-top:1.5rem;"> 285 See also the contributions form at the bottom of the page. 286 </p> 287 `, 288 }, 289 es: { 290 BTN_NETWORK:"Red", BTN_ABOUT:"Acerca de", BTN_PEOPLE:"Equipo", BTN_PARTNERS:"Colaboradores", 291 BTN_ARTWORKS:"Obra de Goya", BTN_BIB:"BibliografÃa", BTN_CITE:"Cómo citar", BTN_MENTIONS:"Menciones", BTN_NEWS:"Noticias", BTN_RELEVANT_LINKS:"Enlaces relevantes", BTN_CONTACT:"Contacto", 292 FUNDING_PARTNERS: "Entidades financiadoras", 293 ACADEMIC_PARTNERS: "Colaboradores académicos", 294 GERDA_HENKEL_NAME: "Fundación Gerda Henkel", 295 SEARCH_PH:"Buscarâ¦", FILTER_PROF_ALL:"Todas las profesiones", FILTER_NAT_ALL:"Todas las nacionalidades", 296 NEW_NODES_TITLE: "Nuevos miembros", 297 MEMBERS_TITLE:"Todos los miembros (A-Z por apellido)", LOADING:"Cargando la redâ¦", LAST_UPDATE:"Ãltima actualización", HELP_TEXT_1: "Haz clic en un nodo (un individuo) o en una arista (una conexión entre dos individuos) para ver los datos que contienen.", HELP_TEXT_2: "(La red puede tardar unos segundos en mostrarse)", SITE_TITLE: "Red Goya", SITE_SUBTITLE: "Cartografiar el impacto de Goya en la Francia y la España del siglo XIX", 298 BTN_SEARCH: "Buscar", 299 BTN_CLUSTER_TOGGLE_ON: "Clúster: activado", 300 BTN_CLUSTER_TOGGLE_OFF: "Clúster: desactivado", 301 SELECT_CLUSTER_OPTION: "Selecciona clúster...", 302 303 CONTRIBUTIONS_TITLE: "Contribuciones", 304 CONTRIBUTIONS_TEXT: ` 305 <p>La Red Goya se concibe como una base de datos colaborativa y abierta.</p> 306 <p>Las contribuciones y sugerencias son bienvenidas, y los colaboradores serán acreditados si asà lo desean.</p> 307 `, 308 309 PARTNERS_TITLE: "Colaboradores", 310 311 NOTARIAL_PROTECTION: "Protegido por acta notarial (Madrid, 2025)", 312 ACCESS_FORM: "Formulario de acceso", 313 RIGHTS_LABEL: "Derechos:", 314 315 CITE_TITLE: "Cómo citar la Red Goya", 316 BIB_TITLE: "BibliografÃa", BIB_INTRO: "Esta sección reúne las fuentes bibliográficas empleadas a lo largo del proyecto. Las referencias que aparecen en los no
316dos y en las conexiones se presentan en forma de citas abreviadas (por ejemplo, Adhémar 1958).", 317 318 //profesiones 319 "Actor":"Actor/Actriz","Archaeologist":"Arqueólogo/a","Anthropologist":"Antropólogo/a","Architect":"Arquitecto/a", "Aristocrat":"Aristócrata","Art critic":"CrÃtico de arte","Art dealer":"Marchante de arte","Art patron":"Mecenas","Art historian":"Historiador/a del arte","Art theorist":"Teórico/a del arte","Artist":"Artista","Banker":"Banquero/a","Bibliographer":"Bibliógrafo","Bibliophile":"Bibliófilo","Businessman":"Empresario/a","Caricaturist":"Caricaturista","Ceramist":"Ceramista","Civil servant":"Funcionario","Clergyman":"Clérigo","Collector":"Coleccionista","Composer":"Compositor/a","Critic":"CrÃtico/a", "Curator":"Conservador de museo", "Designer":"Diseñador/a","Diplomat":"Diplomático/a","Draughtsman":"Dibujante","Economist":"Economista","Editor":"Editor/a", "Engineer":"Ingniero","Engraver":"Grabador/a","Essayist":"Ensayista","Femme de lettres":"Mujer de letras","Hispanist":"Hispanista", "Historian":"Historiador","Illustrator":"Ilustrador", "Printer":"Impresor","Journalist":"Periodista","Jurist":"Jurista","Lawyer":"Abogado","Librarian":"Bibliotecario/a","Librettist":"Libretista","Lithographer":"Litógrafo/a","Lithographic printer":"Impresor/a litográfico/a","Medical doctor":"Médico/a","Military member":"Militar","Musician":"Músico/a","Novelist":"Novelista","Painter":"Pintor/a","Philanthropist":"Filántropo/a","Philosopher":"Filósofo/a","Photographer":"Fotógrafo/a","Pianist":"Pianista","Playwright":"Dramaturgo/a","Poet":"Poeta","Politician":"PolÃtico/a","Print editor":"Editor de grabados","Print dealer":"Marchante de grabados", "Real estate agent":"Agente inmobiliario/a","Salonnière":"Salonnière","Scholar":"Académico/a","Scientist":"CientÃfico/a","Sculptor":"Escultor/a","Secret agent":"Agente secreto/a","Singer":"Cantante","Socialite":"Figura social","Teacher":"Profesor","Translator":"Traductor/a", "Traveller":"Viajero", "Writer":"Escritor/a", 320 321 //nacionalidades 322 "Spanish":"Española","French":"Francesa","British":"Británica","English":"Inglesa","Scottish":"Escocesa","Irish":"Irlandesa","Italian":"Italiana","German":"Alemana","Swiss":"Suiza","Belgian":"Belga","Dutch":"Neerlandesa","Austrian":"AustrÃaca","Portuguese":"Portuguesa","American":"Estadounidense", "Cuban":"Cubana", "Polish":"Polaca", "German / Czech":"Alemana / Checa", 323 324 //CategorÃas en nodos y edges 325 "Life dates":"Fechas de vida","Sex":"Sexo","male":"hombre","female":"mujer","Full name":"Nombre completo","Also known as":"También conocido/a como","Other names":"Otros nombres","Pseudonyms":"Seudónimos", "Profession":"Profesión","Nationality":"Nacionalidad","Born in":"Nace en","Dies in":"Fallece en","Considered as":"Considerado/a como", "Political views":"Ideas polÃticas","Military activity":"Actividad militar","Participates in":"Participa en", "Aristocratic titles":"TÃtulos nobiliarios","Ties with slavery/colonialism":"VÃnculos con esclavitud/colonialismo","Curiosities":"Curiosidades", "Parents":"Padres","Siblings":"Hermanos/as","Married to":"Casado/a con","Partners/lovers":"Parejas/amantes","Children":"Hijos/as", "Friends":"Amigos/as","Rivals":"Rivales","Correspondence":"Correspondencia","Meets":"Encuentros","Studies in":"Estudia en", "Works as":"Trabaja como","Works for":"Trabaja para","Influenced by":"Influido/a por","Follower of":"Seguidor/a de","Masters":"Maestros/as","Students":"Alumnos/as", "Member of":"Miembro de","Founder of":"Fundador/a de","Editor of":"Editor/a de","Writes in":"Escribe en","Collaborates with":"Colabora con", "Registered in":"Registrado/a en","Mentioned in the French press":"Mencionado/a en la prensa francesa","Decorations/awards":"Condecoraciones/premios", "Literary salon or tertulia":"Salón literario o tertulia","Author of":"Autor/a de","Collection":"Colección", "Patronage":"Mecenazgo","Portraits":"Retratos","Exhibitions":"Exposiciones", "Bibliography":"BibliografÃa","Websites":"Páginas web", "Podcasts":"Podcasts","Newspaper archive":"Hemeroteca","Archives":"Archivos","Added":"Añadido","Last modified":"Ãltima modificación", "Identity and Personal Data":"Identidad y datos personales", "Social and Political Identity":"Identidad social y polÃtica", "Family and Personal Relationships":"Familia y relaciones personales", "Education, Profession and Institutions":"Formación, profesión e instituciones", "Artistic/Literary Production and Collection":"Producción artÃstica/literaria y colección", "Exhibitions and Public Reception":"Exposiciones y recepción pública", "Relationship with Goya":"Relación con Goya", "Copies after Spanish artworks":"Copias de obras españolas",
326 "Sources and Documentation":"Fuentes y documentación", "Connections":"Conexiones", "Artistic Activity, Art Collecting and Patronage":"Actividad artÃstica, coleccionismo y mecenazgo", "Geographic Presence and Movement":"Presencia geográfica y desplazamientos", "Relationship to Goya and His Work":"Relación con Goya y su obra", "Portrayed by":"Retratado/a por", "Views on art and cultural heritage":"Reflexiones sobre arte y patrimonio cultural", "Image source":"Origen de la imagen", "In Spain":"En España", "Knows Goya's works":"Conoce la obra de Goya", "Address":"Dirección", "Link to Goya's work":"VÃnculo con la obra de Goya", "Interest in Spanish art":"Interés en el arte español", "Mentions of Goya":"Menciones de Goya", "Collector of Goya's works":"Coleccionista de la obra de Goya", "In Paris":"En ParÃs", "mentions": "Menciones", "Conferences/seminars":"Conferencias/seminarios", "Trips":"Viajes", "Lives in":"Vive en", "Patrons": "Mecenas", "Collector of":"Coleccionista de", "Copies after Goya":"Copias de Goya", "Writes about Goya":"Escribe sobre Goya", "Mentions of Goya":"Menciones de Goya", "Connection": "Conexión", "Type of connection": "Tipo de conexión", "Type of relationship": "Tipo de relación", "Know each other since": "Se conocen desde", "Search": "Buscar", "Collaborations": "Colaboraciones", "They met": "Se encontraron", "direct": "directa", "Artworks bought/sold": "Compraventa de obras", "Interactions": "Interacciones", "Employment/patronage": "Empleo/patronazgo", "Between": "Entre", "and": "y", "Between [X] and [Y]": "Entre [X] y [Y]", "In France": "En Francia", "In Italy": "En Italia", "direct": "directa", "secondary": "secundaria", "acquaintances?": "¿conocidos?", "direct?": "¿directa?", "secondary?": "¿secundaria?", "portraitist-sitter": "retratista-retratado", "employer-employee": "empleador-empleado", "family": "familia", "friends": "amigos", "acquaintances": "conocidos", "master-student": "maestro-alumno", "partners/lovers": "parejas/amantes", "artworks influenced by Goya": "influenciado por Goya", "knows Goya's works": "conoce la obra de Goya", "Sales": "Ventas", "colleagues": "colegas", "copies": "copias", "Nodes": "Nodos", "Connections": "Conexiones", "Access form": "Acceder al formulario", "Unknown": "desconocida", "Visits the Prado Museum": "Visita el Museo del Prado", "Art collection":"Colección de arte", "Artworks bought/sold/given":"Compraventa/cesión de obras", "Artworks influenced by Goya":"Obras influenciadas por Goya", "BnF":"BnF", "Commissions":"Encargos", "Commissions Goya with":"Encarga a Goya", "Common friends":"Amigos comunes", "Comparisons":"Comparaciones", "Connection Strength":"Intensidad de la conexión", "Criticism":"CrÃtica", "Discovers Goya's works":"Descubre la obra de Goya", "Disseminates Goya's works":"Difunde la obra de Goya", "Exchanges":"Intercambios", "Feuds":"Enemistades", "Getty ULAN":"Getty ULAN", "In Madrid":"En Madrid", "Influence":"Influencia", "Influence of Goya":"Influencia de Goya", "Introductions":"Presentaciones", "Link to Goya":"VÃnculo con Goya", "Married in":"Casado/a en", "Mentions the Prado commentaries":"Menciona los comentarios del Prado", "Museum collections":"Colecciones de museos", "Quotations of Goya":"Citas de Goya", "RAH":"RAH", "Rivalry":"Rivalidad", "Shared":"Compartido", "Shows Goya":"Muestra a Goya", "They shared":"Compartieron", "Wikidata":"Wikidata", 327 328 ARTWORKS_TITLE: "La obra de Goya", 329 ARTWORKS_BODY: ` 330 <p> 331 <strong>La cuestión de la atribución en Goya:</strong><br> 332 Al estudiar a Goya conviene tener presente que, debido a la enorme demanda de su obra desde la muerte del artista (que supera con creces la oferta de piezas auténticas), se han producido innumerables copias, imitaciones y pastiches, especialmente en la España y la Francia del siglo XIX. Muchas de estas continúan exhibiéndose como auténticos «Goyas» (más eufemÃsticamente, «atribuido a Goya», o incluso «taller de Goya») en museos de todo el mund
332o, impulsadas por intereses económicos y por el prestigio social. Y cada año aparecen nuevos «Goyas» en galerÃas, entre marchantes y en colecciones privadas. 333 </p> 334 335 <p> 336 <strong>Catálogos razonados, catálogos de exposiciones fundamentales y biografÃa</strong> (véase la sección Bibliography):<br> 337 <ul> 338 <li><strong>Obra completa:</strong> Juliet Wilson-Bareau & Pierre Gassier. <em>Vie et Åuvre de Francisco Goya. L'Åuvre complet illustré. Peintures, dessins, gravure</em>, 1970 (francés); <em>The Life and Complete Work of Francisco Goya: With a Catalogue Raisonné of the Paintings, Drawings and Engravings</em>, 1971 (inglés); <em>Vida y obra de Francisco Goya: reproducción de su obra completa, pinturas, dibujos y grabados</em>, 1974 (español). Véase también el catálogo de la exposición retrospectiva <i>Goya</i>, Martin Schwander (ed.), Fondation Beyeler, 2021 (inglés/español/alemán).</li> 339 <li><strong>Dibujos:</strong> Manuela Mena-Marqués & José Manuel Matilla, <em>Catálogo Razonado de los Dibujos de Francisco de Goya, vol. 2 (1771-1792)</em>, con G. Maurer, G. Solache, J. Carrete, J. Wilson & V. Albarrán, 2018 (español); <em>Goya: dibujos. 'Solo la voluntad me sobra'</em>, cat. exp., 2019 (español/inglés); y Pierre Gassier, <em>Les dessins de Goya. Les albums</em>, 1973 (francés), <em>Francisco Goya. Drawings. The Complete Albums</em>, 1973 (inglés).</li> 340 <li><strong>Estampas:</strong> Tomas Harris, <em>Goya. Engravings and Lithographs</em>, 1964. Véase también Juan Carrete Parrondo & Jesusa Vega, <em>Goya grabador</em>, 1991.</li> 341 <li><strong>BiografÃa</strong>: Janis Tomlinson, <em>Goya: A Portrait of the Artist</em>, 2020 Princeton UP (inglés), 2022 Cátedra (español).</li> 342 </ul> 343 </p> 344 345 <p> 346 <strong>Recursos en lÃnea:</strong><br> 347 <ul> 348 <li><strong>Wikipedia:</strong> una panorámica amplia y colaborativa de las obras de Goya presentada en forma de tabla, con tÃtulo, imagen, fecha, localización y atribución. <a href="https://en.wikipedia.org/wiki/List_of_works_by_Francisco_Goya" target="_blank"> List of Works by Francisco de Goya</a> (inglés) â pinturas y estampas; <a href="https://es.wikipedia.org/wiki/Anexo:Cuadros_de_Goya" target="_blank"> Cuadros de Goya</a> (español) â pintura (no exhaustivo).</li><br> 349 <li><a href="https://fundaciongoyaenaragon.es/" target="_blank"><strong>Fundación Goya en Aragón:</strong></a> catálogo en lÃnea de la obra de Goya elaborado por la Fundación Goya en Aragón (Gobierno de Aragón), que incluye descripciones, procedencia, bibliografÃa e imágenes. Este catálogo, sin embargo, reúne <em>todas</em> las obras que en algún momento se han atribuido a Goya, incluidas copias y pastiches.</li><br> 350 </ul> 351 </p> 352 353 <p> 354 <strong>COLECCIONES GOYA: museos, bibliotecas, iglesias y colecciones privadas:</strong><br> 355 <br> 356 ESPAÃA 357 <li><a href="https://www.museodelprado.es/coleccion/artista/goya-y-lucientes-francisco-de/39568a17-81b5-4d6f-84fa-12db60780812" target="_blank"><strong>Museo del Prado</strong></a> (<em>Goya en el Prado</em>): obras (pintura, estampa y dibujo) conservadas en el Museo del Prado, documentos, referencias impresas y biblioteca digital.</li> 358 <li><a href="https://www.realacademiabellasartessanfernando.com/goya/la-academia-y-goya/" target="_blank"><strong>Real Academia de Bellas Artes de San Fernando</strong></a> (RABASF): historia del vÃnculo de Goya con la Academia, catálogo de obras, <em>CalcografÃa Nacional</em> (producción de las estampas de Goya).</li> 359 <li><strong>Patrimonio Nacional</strong> (patrimonio nacional español): pinturas como <a href="https://www.patrimonionacional.es/colecciones-reales/pintura/retrato-de-carlos-iv-como-cazador" target="_blank"><em>Portrait of Carlos IV as a Hunter</em></a> y <a href="https://www.galeriadelascoleccionesreales.es/obra-de-arte/retrato-de-maria-luisa-de-parma-en-traje-de-corte/19602330-5985-4e7e-81f7-25dce124b6a9" target="_blank"><em>Portrait of MarÃa Luisa de Parma in Court Dress</em></a>, asà como los tapices basados en cartones de Goya, como <a href="https://www.patrimonionacional.es/colecciones-reales/textiles-y-tapiceria/la-era-o-el-verano" target="_blank"><em>The Summer</em></a>.</li> 360 <li><a href="https://www.bne.es/es/blog/blog-bne/post-80" target="_blank"><strong>Biblioteca Nacional de España</strong></a> (BNE): historia de las colecciones de Goya y catálogo.</li> 361 <li><a href="https://coleccion.bde.es/wca/es/secciones/coleccion/artistas/francisco-de-goya-y-lucientes-896.obras.html" target="_blank"><strong>Banco de España</strong></a>
361 (Madrid): pinturas (como los retratos de Francisco Cabarrús, Conde de Floridablanca, XI Conde de Altamira y Carlos III) y estampas (<em>Desastres de la guerra</em>, <em>Tauromaquia</em>, <em>Disparates</em>).</li> 362 <li><a href="https://www.museolazarogaldiano.es/obras-goya-museo-lazaro-galdiano" target="_blank"><strong>Museo Lázaro Galdiano</strong></a> (Madrid): su colección incluye las pinturas <i>El Aquelarre</i> (1797-98) y <i>Las Brujas/El conjuro</i> (1797-98), los bocetos al óleo <i>Santa Isabel de Portugal curando a una enferma</i> (c.1799) y <i>San Hermenegildo en la prisión</i> (1800), y el boceto preparatorio <i>El verano</i> (1786); estampas como <i>El can</i> (c.1820-27); y dibujos como <i>Felipe III</i> (1778), <i>Retrato ecuestre del Conde-Duque de Olivares</i> (c.1776-78), <i>Portrait of Giambattista Casti</i> (c.1802-03), <i>Modo de volar</i> (1815-16), <i>Mendigo ciego con un perro</i> (c.1824) y <i>Mendigo con acordeón</i> (c.1824-28).</li> 363 <li><a href="https://www.museothyssen.org/coleccion/artistas/goya-francisco" target="_blank"><strong>Museo Thyssen Bornemisza</strong></a> (Madrid): biografÃa y colección, que incluye <i>Retrato de Asensio Julià </i> (c.1798), <i>Retrato de Fernando VII</i> (1814-15) y <i>El TÃo Paquete</i> (c.1819-20).</li> 364 <li><a href="https://museogoya.fundacionibercaja.es/goya-en-zaragoza.php" target="_blank"><strong>Museo Goya - Colección Fundación Ibercaja - Museo Camón Aznar</strong></a> (Zaragoza).</li> 365 <li><a href="https://fundacionfuendetodosgoya.org/museo-del-grabado/" target="_blank"><strong>Museo del Grabado</strong></a> (Fuendetodos, Zaragoza): museo de obra gráfica en la casa natal de Goya.</li> 366 <li><strong>IGLESIAS Y CATEDRALES</strong>: Ermita de San Antonio de la Florida (Madrid), Real BasÃlica de San Francisco el Grande (Madrid), Catedral de Valencia, Catedral de Sevilla, BasÃlica del Pilar (Zaragoza), Cartuja del Aula Dei (Zaragoza).</li> 367 <li><strong>COLECCIONES PRIVADAS</strong> (selección): colección Abelló; colección Marqués de la Romana; Duke of Alba (Palacio de Liria); Dukes of Osuna (Alameda de Osuna); colección Carderera; Fundación MarÃa Cristina Masaveu Peterson; Fundación BBVA (Madrid, Palacio del Marqués de Salamanca): <a href="https://www.coleccionbbva.com/es/pintura/443-retrato-de-carlos-iii-cazador/" target="_blank"><em>Carlos III as a Hunter</em></a> y <a href="https://www.coleccionbbva.com/es/pintura/444-don-pantaleon-perez-de-nenin/" target="_blank"><em>Portrait of Don Pantaleón Pérez de NenÃn</em></a>.</li><br> 368 369 FRANCIA<br> 370 <li><a href="https://collections.louvre.fr/en/recherche?q=Francisco%20de%20Goya" target="_blank"><strong>Musée du Louvre</strong>:</a> pinturas, estampas y dibujos.</li> 371 <li><a href="https://www.museegoya.fr/fr/search?p=1&query=Goya" target="_blank"><strong>Musée Goya Castres</strong>:</a> pinturas, estampas, copias tras Goya y documentos. Su colección incluye <a href="https://www.museegoya.fr/fr/notice/894-5-4-l-assemblee-de-la-compagnie-royale-des-philippines-dite-la-junte-des-philippines-7dc13026-e99a-4d4d-a924-afa2204705e9" target="_blank"><i>La Junta de Filipinas</i></a> (L'Assemblée de la Compagnie Royale des Philippines) (1815) (894-5-4), el <a href="https://www.museegoya.fr/fr/notice/894-5-3-portrait-de-francisco-del-mazo-a3f16ab8-79d5-46ea-b3e8-5fb553468f2c" target="_blank"><i>Portrait of Francisco del Mazo</i></a> (1815-19) (894-5-3), y el <a href="https://www.museegoya.fr/fr/notice/894-5-2-autoportrait-aux-lunettes-36227304-cf57-402d-a357-5c5647a9aa7c" target="_blank"><i>Self-portrait with Glasses</i></a> (c.1800) (894-5-2).</li> 372 <li><a href="https://gallica.bnf.fr/selections/fr/html/francisco-de-goya" target="_blank"><strong>Bibliothèque nationale de France</strong></a> (BnF): historia de las colecciones de Goya y catálogo (estampas).</li> 373 <li>Palais des Beaux-arts de Lille: <a href="https://pba.lille.fr/Collections/Chefs-d-OEuvre/Peintures-XVI-sup-e-sup-XXI-sup-e-sup-siecles/Les-Vieilles-Le-Temps" target="_blank"><em>Les vieilles/Le temps</em></a> y <a href="https://pba.lille.fr/Collections/Chefs-d-OEuvre/Peintures-XVI-sup-e-sup-XXI-sup-e-sup-siecles/Les-Jeunes-La-Lettre" target="_blank"><em>Les jeunes/La lettre</em></a>. También un conjunto de los <em>Caprichos</em>.</li> 374 <li><a href="https://www.mbaa.besancon.fr/les-collections/peinture/" target="_blank"><strong>Musée des Beaux-Arts de Besançon</strong></a>: <em>Cannibals watching human remains</em> y <em>Cannibals preparing their victims</em>
374.</li> 375 <li>Musée des Beaux-Arts de Strasbourg: <a href="https://musees-strasbourg.skin-web.org/document/portrait-de-don-bernardo-iriarte/5ee338d7461cda28a3ab1e90?q=Goya&pos=14&pgn=0" target="_blank"><em>Portrait de Don Bernardo de Iriarte</em></a>, asà como estampas (tras Velázquez, <em>Caprichos</em>, <em>Disparates</em>, estampas sueltas como <em>El agarrotado</em>).</li> 376 <li><a href="https://www.musee-agen.fr/recherche?q=Goya&tx_indexedsearch%5Bsubmit_button%5D=OK" target="_blank"><strong>Musée des Beaux-Arts d'Agen</strong></a>: algunas pinturas atribuidas a Goya, como un autorretrato y <em>Le Ballon</em>.</li> 377 <li><a href="https://mbh.bayonne.fr/" target="_blank"><strong>Musée Bonnat-Helleu</strong></a> (Bayona): obras como el dibujo <em>Dos mujeres abrazándose</em> y un retrato de Goya con gafas (atribuido).</li> 378 <li>COLECCIONES PRIVADAS: colección parisina desconocida (los retratos gemelos de Javier Goya y Gumersinda Goicoechea).</li><br> 379 380 EE. UU.<br> 381 <li><a href="https://collections.mfa.org/search/Objects/*/Goya/images?page=1" target="_blank"><strong>Boston Museum of Fine Arts</strong></a> (MoFA): pinturas (<a href="https://www.metmuseum.org/art/collection/search?q=Goya&sortBy=Relevance&searchField=ArtistCulture" target="_blank"><em>Portrait of a Young Man in Brown, possibly Javier Goya</em></a>, 48.558), <a href="https://collections.mfa.org/collections/315210/dpdprints-by-goya/objects" target="_blank">estampas</a> y <a href="https://collections.mfa.org/collections/315215/dpddrawings-by-goya;jsessionid=7527C5D8C33C9415AF5A65EC964E77C1/objects" target="_blank">dibujos</a>.</li> 382 <li><a href="https://www.metmuseum.org/art/collection/search?q=Goya&sortBy=Relevance&searchField=ArtistCulture" target="_blank"><strong>Metropolitan Museum</strong></a> (Nueva York): pinturas, estampas y dibujos.</li> 383 <li><a href="https://www.nortonsimon.org/art/in-focus/goya" target="_blank"><strong>Norton Simon Museum</strong></a> (Pasadena): historia de las colecciones de Goya y catálogo.</li> 384 <li><a href="https://www.frick.org/art/artists/goya" target="_blank"><strong>The Frick Collection</strong></a> (Nueva York): historia de las colecciones de Goya y catálogo.</li> 385 <li><a href="https://hispanicsociety.emuseum.com/search/Goya*" target="_blank"><strong>The Hispanic Society of America</strong></a> (Nueva York): colección de Goya, que incluye pinturas (<i>Portrait of the Duchess of Alba in black</i>, 1797, A102; <i>Portrait of Pedro Mocarte</i>, c.1805, A1890; <i>Portrait of Don Manuel Lapeña</i>, 1799, A99), estampas (<i>Los Caprichos</i>), </i>y dibujos (<i>Majas fighting, a Mano observing</i>, 1796-07, A3309; <i>Majas y majos conversing</i>, 1796-97, A3310; <i>Hay pulgas?</i>, 1796-97, A3316; <i>Buena gente somos los moralistas</i>, 1796-97, A3317; <i>Juan Fernández Navarrete</i>, 1798, LA2223; <i>Tiene prisa de escapar</i>, 1808-14, A3318; <i>Torture of a man</i>, c.1812-20, A3312; <i>Peasant carrying woman</i>, 1812-20, A3315; <i>Tuti li mundi</i>, c.1808-14, A3314; <i>Regocijo</i>, 1819-23, A3308; <i>Old man on a swing</i>, 1823-28, A3313; <i>Woman with 2 children</i>, 1823-28, A3311).</li> 386 <li><a href="https://www.nga.gov/artists/1353-francisco-goya" target="_blank"><strong>National Gallery of Art</strong></a> (Washington, D.C.): pinturas, estampas y dibujos.</li> 387 <li><a href="https://meadowsmuseumdallas.org/collections/pages/objects-1/portfolio/?records=9&query=mfs%20any%20%22Goya%22&sort=0" target="_blank"><strong>The Meadows Museum</strong></a> (Dallas): pinturas (incluidas copias y pastiches).</li> 388 <li><a href="https://www.artic.edu/artists/40615/francisco-jose-de-goya-y-lucientes" target="_blank"><strong>Art Institute of Chicago</strong></a>: pinturas, estampas y dibujos (incluidos pastiches).</li> 389 <li><a href="https://harvardartmuseums.org/collections/person/23169?person=23169" target="_blank"><strong>Harvard Art Museums</strong></a> (Cambridge, Mass.): pinturas, estampas y dibujos (incluidos pastiches).</li> 390 <li><strong>Museo de Arte de Ponce</strong> (Puerto Rico): <a href="https://museoarteponce.org/obras/retrato-de-martin-zapater-portrait-of-martin-zapater/" target="_blank"><em>Portrait of MartÃn Zapater</em></a>.</li><br> 391 392 REINO UNIDO<br> 393 <li><a href="https://www.nationalgallery.org.uk/artists/francisco-de-goya" target="_blank"><strong>National Gallery</strong></a> (Londres): <a href="https://www.nationalgallery.org.uk/paintings/francisco-de-goya-a-picnic" target="_blank"><em>A Picnic</em></a>, <a href="https://www.nationalgallery.org.uk/paintings/francisco-de-goya-a-scene-from-the-forcibly-bewitched" target="_blank"><em>A Scene from 'The Forcibly Bewitched'</em></a>, <a href="https://www.nationalgallery.org.uk/paintings/francisco-de-goya-don-andres-del-peral" target="_blank"><em>Don Andrés del Peral</em></a>, y <a href="https://www.nationalgallery.org.uk/paintings/francisco-de-goya-the-duke-of-wellington" target="_blank"><em>The Duke of Wellington</em></a>. También <a href="https://www.nationalgallery.org.uk/paintings/francisco-de-goya-dona-isabel-de-porcel" target="_blank"><em>Portrait of Isabel de Porcel</em></a>
393 (atribuido).</li> 394 <li><a href="https://thebowesmuseum.org.uk/discover-the-spanish-collection-held-by-the-bowes-museum/" target="_blank"><strong>The Bowes Museum</strong></a> (Durham): pinturas, incluido el retrato de Juan Meléndez Valdés y <i>Interior of a Prison</i>.</li> 395 <li><strong>English Heritage</strong>: Wellington Collection, Apsley House (Londres) (<a href="https://artuk.org/discover/artworks/equestrian-portrait-of-the-1st-duke-of-wellington-17691852-144183" target="_blank">Equestrian Portrait of the 1st Duke of Wellington</a>).</li> 396 <li><a href="https://artuk.org/discover/artworks/el-medico-the-doctor-210121" target="_blank"><strong>National Galleries of Scotland</strong></a> (Edimburgo): <em>The Doctor</em>.</li> 397 <li><strong>The Courtauld Gallery</strong> (Londres): el dibujo <a href="https://courtauld.ac.uk/highlights/cantar-y-bailar-singing-and-dancing/" target="_blank"><em>Singing and Dancing</em></a> y la pintura <a href="https://courtauld.ac.uk/highlights/portrait-of-francisco-de-saavedra/" target="_blank"><em>Portrait of Francisco de Saavedra</em></a>.</li> 398 <li><strong>Pollok House</strong> (Glasgow): serie de 6 pinturas pequeñas de escenas con niños, entre ellas <a href="https://artuk.org/discover/artworks/boys-playing-at-see-saw-84159" target="_blank"><em>Boys Playing at See-Saw</em></a> y <a href="https://artuk.org/discover/artworks/boys-playing-at-soldiers-84160" target="_blank"><em>Boys Playing at Soldiers</em></a>.</li> 399 <li><a href="https://artuk.org/discover/artworks/disparate-de-miedo-261946" target="_blank"><strong>The Higgins</strong></a> (Bedford): «Disparate de miedo» (<em>Disparates</em>).</li><br> 400 401 IRLANDA<br> 402 <li><strong>National Gallery of Ireland</strong> (DublÃn): <a href="https://www.nationalgallery.ie/art-and-artists/highlights-collection/dona-antonia-zarate-francisco-jose-de-goya-y-lucientes-1746" target="_blank"><em>Portrait of Doña Antonia de Zárate</em></a>.</li><br> 403 404 SUIZA<br> 405 <li><strong>Emil Bührle Collection</strong> (Zúrich): <a href="https://buehrle.ch/en/artworks/procession-in-valencia/" target="_blank"><em>Procession in Valencia</em></a>.</li> 406 <li>COLECCIONES PRIVADAS: Rothschild (incluida la versión auténtica de <em>Majas on the Balcony</em>).</li><br> 407 408 ALEMANIA<br> 409 <li><a href="https://recherche.smb.museum/?language=de&limit=15&sort=relevance&controls=none&conditions=AND%2BinvolvedParties%2B%22Francisco+Jos%C3%A9+de+Goya+y+Lucientes%22&collectionKey=NG*" target="_blank"><strong>Staatliche Museen zu Berlin</strong></a>: catálogo de obras, incluidas <em>La Cucaña</em>.</li> 410 <li><a href="https://www.sammlung.pinakothek.de/de/search?phrase=Goya#filters={}" target="_blank"><strong>Alte Pinakothek</strong></a> (Múnich): pinturas, estampas y dibujos (incluidos pastiches).</li><br> 411 412 ITALIA<br> 413 <li><strong>Uffizi</strong> (Florencia): <a href="https://www.virtualuffizi.com/es/retrato-de-la-condesa-de-chinc%C3%B3n.html" target="_blank"><em>Equestrian portrait of MarÃa Teresa de Vallabriga</em></a>.</li> 414 <li><strong>Fondazione Magnani Rocca</strong> (Parma): <a href="https://www.magnanirocca.it/goya-la-famiglia-dell-infante-don-luis/" target="_blank"><em>La familia del Infante Don Luis</em></a>.</li><br> 415 416 </p> 417 `, 418 419 PEOPLE_TITLE: "Equipo", 420 PEOPLE_BODY: ` 421 <p><strong>Directora del proyecto:</strong> Paula Fayos Pérez, investigadora postdoctoral. Doctora en Historia del arte (Universidad de Cambridge, 2019). Miembro asociado de ARCHE (Université de Strasbourg) y de SU+MA (Universidad Complutense de Madrid).</p> 422 423 <p><strong>Comité cientÃfico:</strong> El contenido de la Goya Network se revisa y debate periódicamente con los miembros de un comité cientÃfico integrado por consultores externos de reconocido prestigio académico, que aportan orientación y asesoramiento sobre aspectos especÃficos del proyecto.</p> 424 <ul style="margin-top: 0.5rem; line-height: 1.6;"> 425 <li><strong>Jean Michel Massing</strong>: profesor emérito de Historia del arte en la Universidad de Cambridge, King's College. Especialista en historia del grabado, iconografÃa y arte africano.</li> <br> 426 <li><strong>Aurélie Pétiot</strong>: profesora (lecturer) en la Université Paris Nanterre. Especialista en arte del siglo XIX, artes decorativas, estudios de género y prerrafaelismo.</li> <br> 427 <li><strong>Stephanie Loeb Stepanek</strong>: conservadora emérita de Estampas y Dibujos en el Museum of Fine Arts, Boston. Especialista en la obra gráfica de Goya y en la historia del coleccionismo de Goya en Estados Unidos.</li> <br> 428 <li><strong>Anita L. VerÅ</strong>: doctora en Inteligencia Artificial (Universidad de Cambridge, 2022), informática y fundadora de <a href="https://www.consonant-ai.com" target="_blank">ConsonantAI</a>. Apoyo en el desarrollo técnico del sitio web.</li> 429 </ul> 430 431 <p class="section-heading">Colaboradores</p> 432 <ul> 433 <li><strong>Manuela B. Mena-Marqués</strong>: doctora en Historia del arte (Universidad Complutense de Madrid, 1976). Conservadora del Gabinete de Dibujos y Estampas (1979-1981), subdirectora (1981-1996) y jefa del Ãrea de Conservación de Pintura del siglo XVIII y Goya del Museo del Prado (1996-2019). Contribución: comentarios sobre el sitio web (junio de 2025).</li> <br> 434 <li><strong>Joëlle Arches</strong>, conservadora jefe y directora del <a href="https://www.museegoya.fr/" target="_blank">Musée Goya</a> y del Museo Jean Jaurès, y <strong>Cécile Berthoumieu</strong>, conservadora principal de patrimonio (Attachée principale de conservation du patrimoine). Contribución: comentarios sobre el sitio web; sugerencia de incorporar a Marcel Briguiboul a la red (septiembre de 2025).</li> <br> 435 <li><strong>Stephanie Loeb Stepanek</strong>: conservadora emérita de Estampas y Dibujos, Boston Museum of Fine Arts. Contribución: información sobre una edición temprana de <i>Disasters of War</i>, impresa por Laureano Potenciano (octubre de 2025).</li> 436 </ul> 437 `, 438 439 ABOUT_TITLE: "Acerca de la Goya Network / Red Goya", 440 ABOUT_BODY: ` 441 442 <p><strong>Introducción:</strong> A lo largo de los siglos XVIII y XIX, las redes de artistas, escritores, coleccionistas e intelectuales fueron cruciales tanto para la producción artÃstica como para la difusión y comprensión de figuras históricas. Cartografiar estas conexiones permite entender mejor las dinámicas de sociabilidad y la circulación de ideas. En los últimos años, el análisis de redes se ha convert
442ido en una herramienta importante en Humanidades Digitales para iluminar contextos artÃsticos históricos (véase la sección <a href="#" onclick="showSection('relevant-links'); return false;">Enlaces relevantes</a> para ejemplos de proyectos similares).</p> 443 444 <p><em>The Goya Network</em> se apoya en estos métodos y ofrece una base de datos interactiva que cartografÃa las relaciones sociales y profesionales en torno al artista español Francisco de Goya, con especial atención a sus conexiones con artistas, crÃticos y coleccionistas en la España y la Francia del siglo XIX. A diferencia de los estudios tradicionales centrados en su obra, este proyecto pone el énfasis en el contexto relacional que configuró el legado de Goya, y ofrece una herramienta novedosa para el estudio de las redes sociales de artistas históricos. Como base de datos «viva», facilita la colaboración interdisciplinar y funciona tanto como herramienta de investigación como recurso público, proporcionando una visión del alcance de la influencia de Goya y de la recepción de su obra.</p> 445 446 <p><strong>Origen:</strong> Se trata de un proyecto digital basado en investigación, desarrollado por Paula Fayos-Pérez en el marco de su trabajo postdoctoral sobre las redes sociales y profesionales de Goya. Constituye una continuación de la investigación realizada durante su doctorado (Universidad de Cambridge, 2018) <a href="https://www.repository.cam.ac.uk/items/8d9751ce-aca1-4d35-9436-0820ecbc98d4" target="_blank">(repositorio)</a>, que posteriormente se transformó en un libro: <a href="https://www.ceeh.es/en/publicacion/goyas-caprichos-in-nineteenth-century-france-politics-of-the-grotesque/" target="_blank"><em>Goyaâs Caprichos in Nineteenth-Century France. Politics of the Grotesque</em></a> (CEEH, 2024).</p> 447 448 <p><strong>Financiación:</strong> Este proyecto fue iniciado con el apoyo de una Beca Leonardo 2024 (<a href="https://www.redleonardo.es/beneficiario/paula-fayos-perez/" target="_blank">Beca Leonardo</a>) para Investigación CientÃfica y Creación Cultural, financiada por la <a href="https://www.fbbva.es/" target="_blank">Fundación BBVA.</a> La Fundación BBVA no asume responsabilidad alguna por las opiniones, afirmaciones y contenidos de este proyecto, que son enteramente responsabilidad de sus autores.</p> 449 450 <p>Actualmente el proyecto cuenta con el apoyo de una beca de investigación de la <a href="https://www.gerda-henkel-stiftung.de/" target="_blank">Gerda Henkel Stiftung (Fundación Gerda Henkel).</a></p> 451 452 <p><strong>GuÃa de uso:</strong> Cada nodo (individuo) y cada arista/conexión (vÃnculo entre dos individuos) contiene datos. En los nodos, los usuarios pueden consultar información biográfica, asà como producción artÃstica/literaria, coleccionismo y enlaces a la obra de Goya. En las conexiones se muestra el tipo de relación, correspondencia, colaboraciones y otros datos. Las conexiones en color gris/plateado son conexiones directas (amistades, colegas, familiares, conocidos, etc.), mientras que las conexiones en color dorado son conexiones secundarias (por ejemplo, artistas y escritores que copiaron o escribieron sobre Goya pero no llegaron a conocerlo en persona). El tamaño de los nodos es proporcional al número de conexiones (aristas) que tienen.</p> 453 454 <p><strong>Naturaleza del proyecto:</strong> La Goya Network está concebida como una base de datos colaborativa y de contribución abierta, con el objetivo de convertirse en una herramienta útil para especialistas, estudiantes y el público general interesado en Goya. Se acogen contribuciones de historiadores del arte, historiadores, hispanistas, docentes y conservadores especializados en Goya, en la España del siglo XIX, en la Francia del siglo XIX o en cualquiera de los miembros de la red (por ejemplo, Federico Madrazo, Prosper Mérimée, Eugène Delacroix, etc.). Para enviar una contribución, véase la sección Contributions en el sitio web (más abajo).</p> 455 456 <p><strong>MetodologÃa:</strong> <u>Investigación en Historia del arte</u>: basada en análisis sistemático de fuentes bibliográficas y de archivo (como correspondencia) de la Francia y España del siglo XIX. Los nodos y conexiones de la red fueron construidos mediante la cuidadosa comparación cruzada de publicaciones académicas (monografÃas, artÃculos revisados por pares, catálogos de exposiciones), sitios web institucionales y base
456s de datos de colecciones en lÃnea de museos y fundaciones culturales, y recursos digitales verificados.<br> 457 <u>Desarrollo web</u>: realizado con HTML y JavaScript, con datos estructurados en JSON, y control de versiones y despliegue mediante GitHub; desarrollado con el apoyo de ChatGPT (OpenAI), DeepSeek (High-Flyer) y Claude (Anthropic). Configuración inicial del sitio web en colaboración con Anita VerÅ <a href="https://www.consonant-ai.com" target="_blank">(ConsonantAI)</a>.</p> 458 459 <p><strong>Derechos de autor y licencia:</strong> La Goya Network está protegida por acta notarial (Madrid, 2025), que documenta el proceso de investigación y desarrollo entre 2018 y 2025. 460 Su código y estructura están licenciados bajo <a href="https://creativecommons.org/licenses/by-nc/4.0/" target="_blank" style="color:#66ccff;">CC BY-NC 4.0</a>, lo que permite el uso académico no comercial con la debida atribución a la autora.</p> 461 462 <p><strong>Datos y cookies:</strong> Este sitio web utiliza cookies exclusivamente para Google Analytics, que registra de forma anónima el comportamiento de los visitantes. Utilizamos esta información únicamente para comprender cómo se usa el sitio y mejorar su contenido y navegación. Puedes desactivar las cookies de Google Analytics en todos los sitios web instalando el <a href="https://tools.google.com/dlpage/gaoptout" target="_blank" style="color:#66ccff;">complemento de inhabilitación para navegadores de Google Analytics</a>. Para más información sobre el uso de cookies por parte de Google, consulta la página <a href="https://developers.google.com/analytics/devguides/collection/analyticsjs/cookie-usage" target="_blank" style="color:#66ccff;">Cookies & Google Analytics</a>.</p> 463 `, 464 465 CONTACT_TITLE: "Contacto", 466 CONTACT_BODY: ` 467 <p>Para colaboraciones, sugerencias o contribuciones:</p> 468 469 <p style="margin-top:0.5rem;"> 470 <a href="mailto:[email protected]" style="font-weight:bold;"> 471 [email protected] 472 </a> 473 </p> 474 475 <p style="margin-top:1.5rem;"> 476 Véase también el formulario de contribuciones al final de la página. 477 </p> 478 `, 479 480 NEWS_TITLE: "Noticias", 481 NEWS_BODY: ` 482 <ul style="padding-left:1.2rem; line-height:1.7;"> 483 484 <li> 485 <strong>Julio 2026.</strong> La Goya Network ha sido presentada en el <a href="https://www.eventbrite.co.uk/e/goya-network-tickets-1991696763635?aff=oddtdtcreator" target="_blank">Instituto Cervantes de Londres</a>, en un evento presidido por Manuela B. Mena Marqués. 486 </li> 487 <li> 488 <strong>Mayo de 2026.</strong> El proyecto de investigación que desarrolla la Goya Network ha recibido financiación de la <a href="https://www.gerda-henkel-stiftung.de/" target="_blank">Fundación Gerda Henkel (Alemania)</a>. 489 </li> 490 <li> 491 <strong>Marzo de 2026.</strong> La Goya Network sigue ampliándose con nuevos miembros y colaboraciones. Nueva colaboración: <a href="https://crimic-sorbonne.fr/" target="_blank">CRIMIC (Sorbonne Université)</a>. 492 </li> 493 <li> 494 <strong>Septiembre de 2025.</strong> Se estableció una nueva colaboración con <a href="https://artes-uk.org/" target="_blank">ARTES (Iberian & Latin American Visual Culture Group, Reino Unido)</a>. 495 </li> 496 <li> 497 <strong>Septiembre de 2024.</strong> La Red Goya recibe financiación de la <a href="https://www.redleonardo.es/noticias/paula-fayos-crea-goya-network-beca-leonardo/" target="_blank">Fundación BBVA (Beca Leonardo)</a>. 498 </li> 499 </ul> 500 `, 501 502 MENTIONS_TITLE: "Menciones", 503 MENTIONS_BODY: ` 504 505 <h3 style="margin-top:1.5rem; margin-bottom:0.8rem;">ArtÃculos académicos</h3> 506 <ul style="padding-left:1.2rem; line-height:1.7;"> 507 <li><strong><i>Leonardo</i> (MIT Press): próximamente</strong> â Paula Fayos Pérez & Anita VerÅ, âThe Goya Network: Mapping Goya's Impact in 19th-Century France and Spainâ (en inglés).</li> 508 </ul> 509 510 <h3 style="margin-top:1.5rem; margin-bottom:0.8rem;">Conferencias y presentaciones</h3> 511 <ul style="padding-left:1.2rem; line-height:1.7;"> 512 <li><strong>Instituto Cervantes London (2026)</strong> â 'Goya Network: Francisco de Goya's influence on 19th-century French art and literature' (en inglés) <a href="https://www.eventbrite.co.uk/e/goya-network-tickets-1991696763635?aff=
512oddtdtcreator" target="_blank">(ver evento)</a>.</li> 513 <li><strong>Prado Joven 2025 (Museo del Prado)</strong> â 'El impacto de Goya en el siglo XIX: de Gautier a Madrazo' (en español) <a href="https://www.museodelprado.es/recurso/programa-joven-2025/570e6e48-01fc-48b4-b8f6-270e3b3141f3" target="_blank">(programa)</a>. <a href="https://www.youtube.com/watch?v=jSGfYz08mP83" target="_blank">Ver vÃdeo</a>.</li> 514 <li><strong>Congreso Humanidades Digitales Hispánicas 2025 (Universidad de Valencia)</strong> â 'La Red Goya: Humanidades Digitales para mapear la recepción de Goya en el siglo XIX' (en español) <a href="https://hdh2025.uv.es/static/files/Programa_Congreso_1_8.pdf" target="_blank">(ver programa)</a>.</li> 515 <li><strong>Asociación Humanidades Digitales Hispánicas (2025)</strong> â 'Un café con Paula Fayos Pérez. La Red Goya: Humanidades Digitales para mapear la recepción de Goya en el siglo XIX' (en español) <a href="https://humanidadesdigitaleshispanicas.es/cafe-con-paula-fayos-perez-16-de-diciembre-de-2025/" target="_blank">(ver evento)</a>.</li> 516 </ul> 517 518 <h3 style="margin-top:1.5rem; margin-bottom:0.8rem;">Presencia en lÃnea</h3> 519 <ul style="padding-left:1.2rem; line-height:1.7;"> 520 <li><strong>ARTES UK (Iberian & Latin American Visual Culture Group)</strong> â <a href="https://artes-uk.org/links-2/" target="_blank">la Red Goya incluida entre los enlaces</a> (en inglés).</li> 521 <li><strong>XIX (Société des études romantiques & dix-neuviémistes)</strong> â <a href="https://serd.hypotheses.org/19526" target="_blank">'The Goya Network (Réseau Goya)' </a>.</li> 522 <li><strong>Daumier.de</strong> â <a href="https://www.daumier.de/2025/07/30/das-goya-network/" target="_blank">Das Goya Network</a> (en alemán).</li> 523 <li><strong>Red Leonardo</strong> â <a href="https://www.redleonardo.es/noticias/paula-fayos-crea-goya-network-beca-leonardo/?fbclid=IwY2xjawMuhCNleHRuA2FlbQIxMQBicmlkETFmVHVybzVkRTNnNEZ3bGExAR7q3T7lseZC1CU_pp-vGJWXCjVU8C_gUFvqiVPRd7efUEbjLWnIKRb_WU9Ieg_aem_JIlJ6CO77pnEaRFtx1uKmQ" target="_blank">Paula Fayos crea la Goya Network [...]</a> (en español).</li> 524 </ul> 525 526 <h3 style="margin-top:1.5rem; margin-bottom:0.8rem;">Entrevistas</h3> 527 <ul style="padding-left:1.2rem; line-height:1.7;"> 528 <li><strong><i>Mas De Arte</i></strong> â <a href="https://masdearte.com/especiales/paula-fayos-perez/" target="_blank">Paula Fayos Pérez en Especiales/Fichados</a> (en español).</li> 529 <li><strong><i>El PaÃs</i></strong> â próximamente (en español).</li> 530 </ul> 531 `, 532 533 RELEVANT_LINKS_TITLE: "Enlaces relevantes", 534 RELEVANT_LINKS_BODY: ` 535 536 <h3 style="margin-top:1.5rem; margin-bottom:0.8rem;">Goya</h3> 537 <ul style="padding-left:1.2rem; line-height:1.6;"> 538 <li><a href="https://www.museodelprado.es/coleccion/artista/francisco-de-goya-y-lucientes/39568a17-81b5-4d6f-84fa-12db60780812" target="_blank"><strong>Goya en el Prado</strong></a> â Un recurso digital del artista para aprender sobre sus pinturas, estampas y dibujos en el museo, asà como documentos, referencias impresas y una biblioteca digital (en español).</li> 539 <li><a href="https://www.realacademiabellasartessanfernando.com/goya/la-academia-y-goya/" target="_blank"><strong>Real Academia de Bellas Artes de San Fernando: La Academia y Goya</strong></a> â La historia de Goya con la Real Academia de Bellas Artes de Madrid, asà como una panorámica de las colecciones de Goya, particularmente sus series de estampas (en español).</li> 540 </ul> 541 542 <h3 style="margin-top:1.5rem; margin-bottom:0.8rem;">Siglo XIX / Francia</h3> 543 <ul style="padding-left:1.2rem; line-height:1.6;"> 544 <li><a href="https://www.musee-delacroix.fr/fr/informations-complementaires/liens/societe-des-amis-du-musee" target="_blank"><strong>Société des Amis du musée Delacroix</strong></a> â Dedicada al estudio y promoción de la obra y legado de Eugène Delacroix (francés/inglés).</li> 545 <li><a href="https://prosper-merimee.com/" target="_blank"><strong>Société Mérimée</strong></a> â Una organización y revista académica (<i>Cahiers Mérimée</i>) dedicada a la figura de Prosper Mérimée (en francés).</li> 546 <li><a href="https://www.societedesetudesmarcelinedesbordesvalmore.fr/" target="_blank"><strong>Société des études Marceline Desbordes-Valmore</strong></a> â Promueve el estudio de la poeta y novelista francesa del siglo XIX (en francés).</li> 547 <li><a href="https://www.theophilegautier.fr/" target="_blank"><strong>
547Société Théophile Gautier</strong></a> â Una asociación dedicada a la vida, obra y legado literario de Théophile Gautier (en francés).</li> 548 <li><a href="https://www.daumier.de/" target="_blank"><strong>Daumier.de</strong></a> â Dedicado al estudio y promoción de la obra y legado de Honoré Daumier (en alemán).</li> 549 <li><a href="https://serd.hypotheses.org/" target="_blank"><strong>XIX Société des études romantiques & dix-neuvièmistes</strong></a> â Una sociedad dedicada al estudio del romanticismo y el siglo XIX (en francés).</li> 550 <li><a href="https://19cwwfw.org/" target="_blank"><strong>Nineteenth-Century Women Writers in the Francophone World</strong></a> (de Bénédicte Monicat y Morgane Haesen) â Una base de datos de mujeres escritoras del siglo XIX en el mundo francófono (en inglés).</li> 551 <li><a href="https://19thc-artworldwide.org/" target="_blank"><strong>Nineteenth-Century Art Worldwide</strong></a> â Una revista dedicada al estudio del arte y la cultura visual del siglo XIX (en inglés).</li> 552 </ul> 553 554 <h3 style="margin-top:1.5rem; margin-bottom:0.8rem;">Bases de Datos y Recursos Generales</h3> 555 <ul style="padding-left:1.2rem; line-height:1.6;"> 556 <li><a href="https://historia-hispanica.rah.es/" target="_blank"><strong>Real Academia de la Historia (RAH): Historia Hispánica</strong></a> â Un portal digital creado por la RAH que geolocaliza en un mapa interactivo la historia global de España y de los territorios que formaron parte de la administración española (español/inglés).</li> 557 <li><a href="https://www.getty.edu/" target="_blank"><strong>J. Paul Getty Trust</strong></a> â Incluye el Museo Getty, Getty Research Institute y extensas bases de datos de historia del arte (en inglés).</li> 558 <li><a href="https://www.bnf.fr/" target="_blank"><strong>Bibliothèque nationale de France (BnF)</strong></a> â Biblioteca Nacional de Francia con colecciones digitalizadas incluyendo cartas, manuscritos e impresiones (francés/inglés/español).</li> 559 <li><a href="https://www.bne.es/" target="_blank"><strong>Biblioteca Nacional de España (BNE)</strong></a> â Biblioteca Nacional de España con extensos recursos digitales (en español).</li> 560 </ul> 561 562 <h3 style="margin-top:1.5rem; margin-bottom:0.8rem;">Otros Proyectos de Humanidades Digitales</h3> 563 <ul style="padding-left:1.2rem; line-height:1.6;"> 564 <li><a href="https://arkivet.thorvaldsensmuseum.dk/en" target="_blank"><strong>Thorvaldsen Letter Archive</strong></a> â CartografÃa de la correspondencia y redes sociales del escultor neoclásico Bertel Thorvaldsen (inglés/danés).</li> 565 <li><a href="http://republicofletters.stanford.edu/casestudies/index.html" target="_blank"><strong>Mapping the Republic of Letters</strong></a> (Universidad de Stanford) â Una visualización interactiva de redes intelectuales en la Europa moderna temprana (en inglés).</li> 566 <li><a href="http://emlo.bodleian.ox.ac.uk/home" target="_blank"><strong>Early Modern Letters Online (EMLO)</strong></a> (Universidad de Oxford) â Un catálogo y análisis de la correspondencia del perÃodo moderno temprano (en inglés).</li> 567 <li><a href="https://mappingrome.com/" target="_blank"><strong>Mapping Rome</strong></a> â Visualización de redes sociales, económicas y culturales en la antigüedad romana (en inglés).</li> 568 <li><a href="https://vangoghworldwide.org/" target="_blank"><strong>Van Gogh Worldwide</strong></a> â Una plataforma colaborativa que conecta las obras de Van Gogh, sus cartas e impacto cultural en instituciones de todo el mund
568o (en inglés).</li> 569 <li><a href="https://womensbios.lib.virginia.edu/index.html" target="_blank"><strong>Collective Biographies of Women</strong></a> (Alison Booth, Universidad de Virginia) â Agregación de datos biográficos sobre mujeres en múltiples disciplinas y perÃodos (en inglés).</li> 570 </ul> 571 572 `, 573 574 CONTACT_TITLE: "Contacto", 575 }, 576 }; 577 578 function t(k){ return (I18N[CURRENT_LANG] && I18N[CURRENT_LANG][k]) || k; } 579 580 function applyUIStrings(){ 581 // Botones del menú superior 582 const map = [ 583 ['.nav-buttons button[onclick*="network"]','BTN_NETWORK'], 584 ['.menu-content button[onclick*="about"]','BTN_ABOUT'], 585 ['.menu-content button[onclick*="people"]','BTN_PEOPLE'], 586 ['.menu-content button[onclick*="partners"]','BTN_PARTNERS'], 587 ['.menu-content button[onclick*="news"]','BTN_NEWS'], 588 ['.menu-content button[onclick*="mentions"]','BTN_MENTIONS'], 589 ['.menu-content button[onclick*="artworks"]','BTN_ARTWORKS'], 590 ['.menu-content button[onclick*="bibliography"]','BTN_BIB'], 591 ['.menu-content button[onclick*="relevant-links"]','BTN_RELEVANT_LINKS'], 592 ['.menu-content button[onclick*="cite"]','BTN_CITE'], 593 ['.menu-content button[onclick*="contact"]','BTN_CONTACT'], 594 ['#searchInput::placeholder','SEARCH_PH'], 595 ['.search-button','BTN_SEARCH'], 596 ]; 597 map.forEach(([sel,key])=>{ 598 if (sel.endsWith('::placeholder')) { 599 const id = sel.replace('::placeholder',''); 600 const el = document.querySelector(id); 601 if (el) el.setAttribute('placeholder', t(key)); 602 } else { 603 const el = document.querySelector(sel); 604 if (el) el.textContent = t(key); 605 } 606 }); 607 608 // Filtros 609 const profAll = document.querySelector('#professionFilter option[value=""]'); 610 if (profAll) profAll.textContent = t('FILTER_PROF_ALL'); 611 const natAll = document.querySelector('#nationalityFilter option[value=""]'); 612 if (natAll) natAll.textContent = t('FILTER_NAT_ALL'); 613 614 // Traduce el texto visible de cada opción (sin tocar el value) 615 document.querySelectorAll('#professionFilter option').forEach(opt => { 616 if (!opt.value) return; 617 opt.textContent = t(opt.value).toLowerCase(); 618 }); 619 620 document.querySelectorAll('#nationalityFilter option').forEach(opt => { 621 if (!opt.value) return; 622 opt.textContent = t(opt.value); 623 }); 624 625 // TÃtulo "Members â¦" 626 const membersHeading = document.querySelector('#membersSection .section-heading'); 627 if (membersHeading) membersHeading.textContent = t('MEMBERS_TITLE'); 628 629 // Mensaje de carga 630 const loading = document.getElementById('loadingMessage'); 631 if (loading) loading.firstChild.textContent = t('LOADING')+' '; 632 633 // Guarda etiqueta para "Last update" 634 window.LAST_UPDATE_LABEL = t('LAST_UPDATE'); 635 636 // Textos declarados con data-i18n 637 const HTML_I18N_KEYS = new Set([ 638 'ARTWORKS_BODY', 639 'PEOPLE_BODY', 640 'ABOUT_BODY', 641 'CONTRIBUTIONS_TEXT', 642 'NEWS_BODY', 643 'MENTIONS_BODY', 644 'RELEVANT_LINKS_BODY', 645 'CONTACT_BODY', 646 // añade aquà futuras secciones largas con HTML, ej: 647 // 'ABOUT_BODY', 'PARTNERS_BODY' 648 ]); 649 650 document.querySelectorAll('[data-i18n]').forEach(el => { 651 const key = el.getAttribute('data-i18n'); 652 if (!key) return; 653 654 const val = t(key); 655 656 // Si el string contiene HTML, hay que usar innerHTML 657 if (HTML_I18N_KEYS.has(key)) { 658 el.innerHTML = val; 659 } else { 660 el.textContent = val; 661 } 662 }); 663 } 664 665 window.setLanguage = function(lang='en') { 666 CURRENT_LANG = (lang === 'es') ? 'es' : 'en'; 667 localStorage.setItem('lang', CURRENT_LANG); 668 applyUIStrings(); 669 if (typeof window.refreshNodeInfoLabels === 'function') window.refreshNodeInfoLabels(); 670 location.reload(); 671 }; 672 673 function sortSelectOptionsAlphabetically(selectId) { 674 const select = document.getElementById(selectId); 675 if (!select) return; 676 677 const options = Array.from(select.options); 678 679 // Mantener la primera opción tipo "All professions" 680 const first = options.shift(); 681 682 options.sort((a, b) => 683 a.textContent.localeCompare(b.textContent, 'es', { sensitivity: 'base' }) 684 ); 685 686 select.innerHTML = ''; 687 select.appendChild(first); 688 options.forEach(opt => select.appendChild(opt)); 689 } 690 691 // Helper para traducir labels de panel 692 function labelI18N(label){ return t(label); } 693 // ====== /I18N ====== 694 695 // --- Forzar re-render del panel lateral con el idioma actual --- 696 window.__lastSelection = null;
696 // guardaremos el último nodo/arista mostrados 697 698 window.refreshNodeInfoLabels = function () { 699 const info = document.getElementById('nodeInfo'); 700 if (!info) return; 701 702 // Si tenÃamos un nodo seleccionado, re-emite el click para regenerar el HTML con t(...) 703 if (window.__lastSelection && window.__lastSelection.type === 'node' && window.__lastSelection.id) { 704 window.VIS_NETWORK.emit('click', { nodes: [window.__lastSelection.id], edges: [] }); 705 return; 706 } 707 708 // Si tenÃamos una arista seleccionada, re-emite el click para regenerar 709 if (window.__lastSelection && window.__lastSelection.type === 'edge' && window.__lastSelection.id) { 710 window.VIS_NETWORK.emit('click', { nodes: [], edges: [window.__lastSelection.id] }); 711 return; 712 } 713 714 // Si no habÃa selección, vuelve al panel por defecto y re-aplica los textos 715 if (typeof window.showDefaultNodeInfo === 'function') window.showDefaultNodeInfo(); 716 applyUIStrings(); // actualiza "Members (AâZâ¦)", placeholder, etc. 717 }; 718 719let nodes, edges; // ð Hacemos estas variables globales 720 721function autoLinkNames(text, nodesMap) { 722 if (!text || typeof text !== "string") return text; 723 724 // Sustituye saltos de lÃnea invisibles por espacio 725 text = text.replace(/\r?\n|\r/g, " "); 726 727 // Divide el texto en partes: enlaces HTML intactos y el resto 728 const splitParts = text.split(/(<a [^>]+>.*?<\/a>)/g); 729 730 const processed = splitParts.map(part => { 731 if (part.startsWith('<a ')) return part; // ya es un enlace, no tocar 732 733 Object.keys(nodesMap).forEach(name => { 734 const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); 735 const regex = new RegExp(`(?<![\\w>])(${escapedName})(?![\\w<])`, "g"); 736 part = part.replace( 737 regex, 738 `<a href="#" style="color:#66ccff" onclick="focusNode('${nodesMap[name].id}')">$1</a>` 739 ); 740 }); 741 742 return part; 743 }); 744 745 return processed.join(''); 746} 747 748function processMarkdownLinks(text) { 749 if (!text || typeof text !== "string") return text; 750 751 // Preserve italics 752 text = text.replace(/<i>/g, '%%%ITALIC_OPEN%%%').replace(/<\/i>/g, '%%%ITALIC_CLOSE%%%'); 753 754 // Format 1: [text](url) 755 text = text.replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g, 756 '<a href="$2" target="_blank" style="color:#66ccff;">$1</a>'); 757 758 // Format 2: (text)[url] 759 text = text.replace(/\(([^)]+)\)\[(https?:\/\/[^\]]+)\]/g, 760 '(<a href="$2" target="_blank" style="color:#66ccff;">$1</a>)'); 761 762 // Format 3: text [url] (fallback) 763 text = text.replace(/(\b[^\s\[\]]+\b)\s*\[(https?:\/\/[^\]]+)\]/g, 764 '<a href="$2" target="_blank" style="color:#66ccff;">$1</a>'); 765 766 // Restore italics 767 text = text.replace(/%%%ITALIC_OPEN%%%/g, '<i>').replace(/%%%ITALIC_CLOSE%%%/g, '</i>'); 768 769 return text; 770} 771 772function isImageUrl(u) { 773 if (!u) return false; 774 const s = String(u).trim(); 775 return /\.(png|jpe?g|gif|webp|svg)(\?.*)?$/i.test(s); 776} 777 778// Add these two functions at the top of network.js 779window.search = function(nodeId) { 780 if (!nodeId || !window.VIS_NETWORK || !nodes.get(nodeId)) return; 781 782 if (typeof window.__GN_clearHighlights === 'function') { 783 window.__GN_clearHighlights(); 784 } 785 786 window.VIS_NETWORK.stopSimulation?.(); 787 788 const pos = window.VIS_NETWORK.getPosition(nodeId); 789 if (!pos) return; 790 791 window.VIS_NETWORK.unselectAll(); 792 793 // Use moveTo with moderate zoom (show neighboring context) - move immediately 794 window.VIS_NETWORK.moveTo({ 795 position: pos, 796 scale: window.VIS_NETWORK.getScale() * 1.2 797 }); 798 799 setTimeout(() => { 800 window.VIS_NETWORK.selectNodes([nodeId]); 801 window.VIS_NETWORK.emit('click', { 802 nodes: [nodeId], 803 edges: [], 804 pointer: { DOM: { x: 0, y: 0 }, canvas: { x: 0, y: 0 } } 805 }); 806 }, 650); 807}; 808 809// =============================== 810// Dropdown Menu Handlers 811// =============================== 812window.toggleMenu = function(event, menuId) { 813 event.stopPropagation(); 814 const menu = document.getElementById(menuId); 815 if (menu) { 816 // Close all other menus first 817 document.querySelectorAll('.menu-content.show').forEach(m => { 818 if (m.id !== menuId) m.classList.remove('show'); 819 }); 820 menu.classList.toggle('show'); 821 } 822}; 823 824window.closeAllMenus = function() { 825 document.querySelectorAll('.menu-content.show').forEach(m => { 826 m.classList.remove('show'); 827 }); 828}; 829 830// Close menus when clicking outside 831document.addEventListener('click', function(event) { 832 if (!event.target.closest('.menu-dropdown')) { 833 window.closeAllMenus(); 834 } 835}); 836 837// =============================== 838// Dropdown Menu Handler 839// =============================== 840window.toggleMenuDropdown = function() { 841 const menuContent = document.getElementById('menuContent'); 842 if (menuContent) { 843 menuContent.classList.toggle('show'); 844 } 845}; 846 847window.closeMenuDropdown = function() { 848 const menuContent = document.getElementById('menuContent'); 849 if (menuContent) { 850 menuContent.classList.remove('show'); 851 } 852}; 853 854// Close menu when clicking outside 855document.addEventListener('click', function(event) { 856 const menuDropdown = document.querySelector('.menu-dropdown'); 857 if (menuDropdown && !menuDropdown.contains(event.target)) { 858 const menuContent = document.getElementById('menuContent'); 859 if (menuContent) { 860 menuContent.classList.remove('show'); 861 } 862 } 863}); 864 865// =============================== 866// Filters: highlight + right panel 867// =============================== 868let __filterHighlightedIds = []; 869let __filterDefaultNodeInfoHTML = null; 870 871// Save current nodeInfo as "default" once 872function __ensureDefaultNodeInfoSnapshot() { 873 if (__filterDefaultNodeInfoHTML) return; 874 const nodeInfoEl = document.getElementById("nodeInfo"); 875 if (nodeInfoEl) __filterDefaultNodeInfoHTML = nodeInfoEl.innerHTML; 876} 877 878function __escapeHtml(s) { 879 return String(s ?? "") 880 .replaceAll("&", "&") 881 .replaceAll("<", "<") 882 .replaceAll(">", ">") 883 .replaceAll('"', """) 884 .replaceAll("'", "'"); 885} 886 887function __clearFilterHighlights() { 888 if (!window.nodes || !__filterHighlightedIds.length) return; 889 890 // Restore ONLY what this filter changed (border + borderWidth) 891 const updates = __filterHighlightedIds.map(id => ({ 892 id, 893 color: { border: "#2B7CE9" }, 894 borderWidth: 2 895 })); 896 897 nodes.update(updates); 898 __filterHighlightedIds = []; 899} 900 901window.renderFilterPanel = function ({ professionFilter, nationalityFilter, matchingNodeIds }) { 902 const nodeInfo = document.getElementById('nodeInfo'); 903 if (!nodeInfo) return; 904 905 const total = nodes.get().length; 906 907 // Si no hay filtros activos, vuelve al panel por defecto 908 if (!professionFilter && !nationalityFilter) { 909 if (typeof window.showDefaultNodeInfo === 'function') window.showDefaultNodeInfo(); 910 window.__lastSelection = null; 911 return; 912 } 913 914 if (count === 0) { 915 html += `<p style="color:#ccc;">No nodes match the selected filters.</p>`; 916 } else { 917 html += `<ul style="margin-top:0.4rem;">`;
918 items.forEach(it => { 919 html += `<li><a href="#" style="color:#66ccff" onclick="focusNode('${it.id}'); return false;">${it.name}</a></li>`; 920 }); 921 html += `</ul>`; 922 } 923 924 html += `</div>`; 925 nodeInfo.innerHTML = html; 926 927 // Guardar estado 928 window.__lastSelection = { type: 'filter', professionFilter, nationalityFilter, ids: (matchingNodeIds || []).slice() }; 929}; 930 931// ============================== 932// FILTER HELPERS (PEGAR ENCIMA de window.filterGraph) 933// ============================== 934function __gnFold(s) { 935 return String(s || '') 936 .normalize('NFD').replace(/[\u0300-\u036f]/g, '') 937 .toLowerCase() 938 .trim(); 939} 940 941function __gnTokenizeList(val) { 942 if (!val) return []; 943 if (Array.isArray(val)) return val.flatMap(__gnTokenizeList); 944 945 // Split por comas, punto y coma, slash, etc. 946 return String(val) 947 .split(/[,;\/|]/g) 948 .map(x => __gnFold(x)) 949 .filter(Boolean); 950} 951 952function __gnVariants(term) { 953 const t = __gnFold(term); 954 const out = new Set([t]); 955 956 // singular/plural simples 957 if (t.endsWith('s')) out.add(t.slice(0, -1)); 958 else out.add(t + 's'); 959 960 if (t.endsWith('es')) out.add(t.slice(0, -2)); 961 else out.add(t + 'es'); 962 963 return out; 964} 965 966// Variantes ES/EN para nacionalidades (para igualar conteos ES vs EN) 967const __GN_NAT_SYNONYMS = { 968 spanish: ['spanish', 'espanol', 'español', 'espanola', 'española'], 969 french: ['french', 'frances', 'francés', 'francesa', 'française', 'francais', 'français'], 970 british: ['british', 'ingles', 'inglés', 'inglesa', 'ingleses', 'britanico', 'británico', 'britanica', 'británica'], 971 german: ['german', 'aleman', 'alemán', 'alemana', 'deutsch'], 972 italian: ['italian', 'italiano', 'italiana'], 973 irish: ['irish', 'irlandes', 'irlandés', 'irlandesa'], 974 swiss: ['swiss', 'suizo', 'suiza'], 975 cuban: ['cuban', 'cubano', 'cubana'], 976 polish: ['polish', 'polaco', 'polaca'] 977}; 978 979// Variantes ES/EN para profesiones (mÃnimo: arregla âart dealerâ) 980const __GN_PROF_SYNONYMS = { 981 'art dealer': ['art dealer', 'art dealers', 'marchante de arte', 'marchantes de arte', 'marchante', 'marchantes'] 982}; 983 984// Clave de orden por apellido (versión âglobalâ equivalente a la lógica de Members list) 985function __gnSurnameKeyForSort(name) { 986 if (!name) return ''; 987 const exactName = String(name).trim(); 988 const forceMap = window.__GN_SURNAME_FORCE_BY_FULLNAME || 989 ((typeof SURNAME_FORCE_BY_FULLNAME !== 'undefined') ? SURNAME_FORCE_BY_FULLNAME : null); 990 const forced = forceMap ? (forceMap[name] || forceMap[exactName]) : null; 991 if (forced) return __gnFold(forced); 992 993 let base = String(name) 994 .replace(/\(.*?\)/g, '') 995 .split(',')[0] 996 .replace(/[.]/g, ' ') 997 .replace(/\s+/g, ' ') 998 .trim(); 999 1000 const fold = __gnFold;
1001 const tokens = base.split(' ').filter(Boolean); 1002 if (!tokens.length) return ''; 1003 1004 const tf = tokens.map(fold); 1005 1006 const titles = new Set([ 1007 'duchess','duke','count','countess','marquis','marchioness', 1008 'queen','king','prince','princess','emperor','empress' 1009 ]); 1010 const roman = /^[IVXLCDM]+$/i; 1011 1012 // âDuchess of Abrantesâ => Abrantes 1013 if (tf.some(t => titles.has(t)) && tf.includes('of') && tokens.length >= 2) { 1014 return fold(tokens[tokens.length - 1]); 1015 } 1016 1017 // âX y Yâ => toma el anterior a 'y' 1018 const yIndex = tf.indexOf('y'); 1019 if (yIndex > 0 && yIndex < tf.length - 1) { 1020 return fold(tokens[yIndex - 1]); 1021 } 1022 1023 // Si empieza por inicial âA. Dutuitâ => usar último token 1024 // (ya quitamos puntos arriba, asà que quedará âA Dutuitâ) 1025 if (tokens.length >= 2 && tokens[0].length === 1) { 1026 return fold(tokens[tokens.length - 1]); 1027 } 1028 1029 // por defecto: último token que no sea romano 1030 for (let i = tokens.length - 1; i >= 0; i--) { 1031 const t = fold(tokens[i]); 1032 if (!roman.test(t)) return t; 1033 } 1034 return fold(tokens[tokens.length - 1]); 1035} 1036 1037function __gnMatchesProfession(nodeProf, selectedProf) { 1038 if (!selectedProf) return true; 1039 1040 const sel = __gnFold(selectedProf); 1041 const candidates = new Set(); 1042 1043 // 1) valor tal cual + variantes 1044 for (const v of __gnVariants(sel)) candidates.add(v); 1045 1046 // 2) sinónimos explÃcitos 1047 if (__GN_PROF_SYNONYMS[sel]) { 1048 __GN_PROF_SYNONYMS[sel].forEach(s => __gnVariants(s).forEach(v => candidates.add(__gnFold(v)))); 1049 } 1050 1051 // 3) si existe función t() (i18n), añade traducción como posible match 1052 try { 1053 if (typeof t === 'function') { 1054 const tr = __gnFold(t(selectedProf)); 1055 if (tr) __gnVariants(tr).forEach(v => candidates.add(v)); 1056 if (__GN_PROF_SYNONYMS[tr]) { 1057 __GN_PROF_SYNONYMS[tr].forEach(s => __gnVariants(s).forEach(v => candidates.add(__gnFold(v)))); 1058 } 1059 } 1060 } catch (_) {} 1061 1062 const profTokens = __gnTokenizeList(nodeProf); 1063 return profTokens.some(tok => candidates.has(tok)); 1064} 1065 1066function __gnMatchesNationality(nodeNat, selectedNat) { 1067 if (!selectedNat) return true; 1068 1069 const sel = __gnFold(selectedNat); 1070 const candidates = new Set([sel]); 1071 1072 // añade sinónimos ES/EN si existen 1073 if (__GN_NAT_SYNONYMS[sel]) { 1074 __GN_NAT_SYNONYMS[sel].forEach(s => candidates.add(__gnFold(s))); 1075 } 1076 1077 // traducción i18n si existiera 1078 try { 1079 if (typeof t === 'function') { 1080 const tr = __gnFold(t(selectedNat)); 1081 if (tr) candidates.add(tr); 1082 if (__GN_NAT_SYNONYMS[tr]) { 1083 __GN_NAT_SYNONYMS[tr].forEach(s => candidates.add(__gnFold(s))); 1084 } 1085 } 1086 } catch (_) {} 1087 1088 const natTokens = __gnTokenizeList(nodeNat); 1089 // normalmente nationality es un único valor; pero lo tratamos igual 1090 1091 // Check for exact token match first 1092 if (natTokens.some(tok => candidates.has(tok))) return true; 1093 1094 // Also check for word-level matches within tokens (for cases like "French (with Irish ancestry)") 1095 const natFolded = __gnFold(nodeNat); 1096 for (const candidate of candidates) { 1097 // Use word boundary regex to find the candidate word within the nationality string 1098 const regex = new RegExp(`\\b${candidate}\\b`); 1099 if (regex.test(natFolded)) return true; 1100 } 1101 1102 return false; 1103} 1104 1105function __gnRenderFilterPanel({ professionFilter, nationalityFilter, matchingNodeIds, totalCount }) { 1106 const nodeInfo = document.getElementById('nodeInfo'); 1107 if (!nodeInfo) return; 1108 1109 // Si no hay filtros activos: vuelve al panel por defecto 1110 if (!professionFilter && !nationalityFilter) { 1111 if (typeof window.showDefaultNodeInfo === 'function') window.showDefaultNodeInfo(); 1112 return; 1113 } 1114 1115 const parts = []; 1116 if (professionFilter) parts.push(`<strong>Profession:</strong> ${professionFilter}`); 1117 if (nationalityFilter) parts.push(`<strong>Nationality:</strong> ${nationalityFilter}`); 1118 1119 const items = (matchingNodeIds || []) 1120 .map(id => nodes.get(id)) 1121 .filter(Boolean) 1122 // mostrar SIEMPRE el id (nombre completo) 1123 .map(n => ({ id: n.id, name: n.id })) 1124 // ordenar por apellido (pero sin mutilar el nombre mostrado) 1125 .sort((a, b) => {
1126 const ka = __gnSurnameKeyForSort(a.name); 1127 const kb = __gnSurnameKeyForSort(b.name); 1128 if (ka !== kb) return ka.localeCompare(kb, undefined, { sensitivity: 'base' }); 1129 return a.name.localeCompare(b.name, undefined, { sensitivity: 'base' }); 1130 }); 1131 1132 const count = items.length; 1133 1134 let html = ` 1135 <div class="node-overview"> 1136 <div>${parts.join(' | ')}</div> 1137 <div>${count} nodes out of ${totalCount}</div> 1138 </div> 1139 <div class="section-heading">Results (AâZ)</div> 1140 <div style="line-height:1.6;"> 1141 `; 1142 1143 if (!count) { 1144 html += `<p style="color:#ccc;">No nodes match the selected filters.</p>`; 1145 } else { 1146 html += `<ul style="margin-top:0.4rem;">`; 1147 items.forEach(it => { 1148 html += `<li><a href="#" style="color:#66ccff" onclick="focusNode('${it.id}'); return false;">${it.name}</a></li>`; 1149 }); 1150 html += `</ul>`; 1151 } 1152 1153 html += `</div>`; 1154 nodeInfo.innerHTML = html; 1155} 1156 1157// ============================== 1158// REEMPLAZAR COMPLETO: window.filterGraph 1159// ============================== 1160function splitAndNormalizeList(v) { 1161 if (!v) return []; 1162 const raw = Array.isArray(v) ? v : String(v).split(/[;,]/); // soporta coma y punto y coma 1163 return raw 1164 .map(s => String(s).trim().toLowerCase()) 1165 .filter(Boolean); 1166} 1167 1168// Convierte valores en ES (p.ej. "marchante de arte") a la clave canónica ("art dealer"), 1169// usando los <option value="..."> del selector + los textos I18N. 1170function buildCanonicalMap(selectId, lang) { 1171 const map = Object.create(null); 1172 const sel = document.getElementById(selectId); 1173 if (!sel) return map; 1174 1175 const dict = (I18N && I18N[lang]) ? I18N[lang] : {}; 1176 1177 [...sel.options].forEach(opt => { 1178 if (!opt.value) return; // salta "All ..." 1179 const canonical = opt.value.trim().toLowerCase(); // p.ej. "art dealer" 1180 map[canonical] = canonical; 1181 1182 // texto visible del option (si está traducido por applyUIStrings) 1183 if (opt.textContent) map[opt.textContent.trim().toLowerCase()] = canonical; 1184 1185 // traducción desde I18N (por si el dato viene en ES pero el option aún no) 1186 if (dict[opt.value]) map[String(dict[opt.value]).trim().toLowerCase()] = canonical; 1187 }); 1188 1189 return map; 1190} 1191window.filterGraph = function() { 1192 const professionFilter = document.getElementById('professionFilter')?.value || ''; 1193 const nationalityFilter = document.getElementById('nationalityFilter')?.value || ''; 1194 1195 // 1) limpiar highlights previos (usa tu clearHighlights si existe; si no, usa el âexpuestoâ) 1196 if (typeof clearHighlights === 'function') clearHighlights(); 1197 else if (typeof window.__GN_clearHighlights === 'function') window.__GN_clearHighlights(); 1198 1199 // 2) sin filtros: salir + restaurar panel 1200 if (!professionFilter && !nationalityFilter) { 1201 __gnRenderFilterPanel({ professionFilter:'', nationalityFilter:'', matchingNodeIds:[], totalCount: (window.__GN_DATA?.nodes?.length || nodes.get().length) }); 1202 return; 1203 } 1204 1205 // 3) total ârealâ desde JSON (evita 207 vs 202) 1206 const baseNodes = (window.__GN_DATA?.nodes && Array.isArray(window.__GN_DATA.nodes)) 1207 ? window.__GN_DATA.nodes 1208 : nodes.get(); 1209 1210 const totalCount = baseNodes.length; 1211 1212 // 4) matching robusto (case-insensitive + listas + ES/EN variantes) 1213 const matchingIds = baseNodes 1214 .filter(n => __gnMatchesProfession(n.profession, professionFilter) && __gnMatchesNationality(n.nationality, nationalityFilter)) 1215 .map(n => n.id); 1216 1217 // 5) si no hay resultados, renderiza panel (sin alert) 1218 if (!matchingIds.length) { 1219 __gnRenderFilterPanel({ professionFilter, nationalityFilter, matchingNodeIds: [], totalCount }); 1220 return; 1221 } 1222 1223 // 6) resaltar en rojo (borde rojo + ancho) 1224 matchingIds.forEach(id => { 1225 const node = nodes.get(id); 1226 if (!node) return; 1227 nodes.update({ 1228 id, 1229 color: { ...(node.color || {}), border: 'red' }, 1230 borderWidth: 4 1231 }); 1232 }); 1233 1234 // 7) foco en el primer match 1235 const firstId = matchingIds[0]; 1236 try { 1237 const scale = window.VIS_NETWORK.getScale(); 1238 const pos = window.VIS_NETWORK.getPosition(firstId); 1239 window.VIS_NETWORK.moveTo({ position: pos, scale, animation: { duration: 500 } }); 1240 } catch (_) {} 1241 1242 // 8) registra lastHighlightedNodes para tu sistema de âclearHighlightsâ 1243 if (typeof window.__GN_setLastHighlightedNodes === 'function') { 1244 window.__GN_setLastHighlightedNodes(matchingIds); 1245 } else if (typeof lastHighlightedNodes !== 'undefined') { 1246 lastHighlightedNodes = matchingIds; 1247 } 1248 1249 // 9) pinta panel derecho 1250 __gnRenderFilterPanel({ professionFilter, nationalityFilter, matchingNodeIds: matchingIds, totalCount }); 1251}; 1252 1253// ===== HASH NAVIGATION SETUP ===== 1254let __hashProcessed = false;
1255 1256// ===== GLOBAL HELPER FUNCTIONS FOR HASH NAVIGATION ===== 1257function slugifyName(name) { 1258 return String(name) 1259 .normalize('NFD') 1260 .replace(/[\u0300-\u036f]/g, '') // Remove accents 1261 .replace(/ /g, '_') // Replace spaces with underscores 1262 .toLowerCase(); // Make lowercase 1263} 1264 1265function unslugifyName(slug) { 1266 return String(slug).replace(/_/g, ' '); 1267} 1268 1269function normalizeForComparison(str) { 1270 if (!str || typeof str !== 'string') { 1271 return ''; 1272 } 1273 return str.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase(); 1274} 1275 1276function makeNodeHash(nodeId) { 1277 return '#node/' + slugifyName(nodeId); 1278} 1279 1280function makeEdgeHash(edgeId) { 1281 if (typeof edges === 'undefined' || !edges) return ''; 1282 const edge = edges.get(edgeId); 1283 if (!edge) return ''; 1284 1285 if (typeof nodes === 'undefined' || !nodes) return ''; 1286 const fromNode = nodes.get(edge.from); 1287 const toNode = nodes.get(edge.to); 1288 if (!fromNode || !toNode) return ''; 1289 1290 const a = fromNode.id; 1291 const b = toNode.id; 1292 1293 // Ordenar para que el hash sea consistente 1294 const ordered = [a, b].sort((x, y) => 1295 x.localeCompare(y, undefined, { sensitivity: 'base' }) 1296 ); 1297 1298 return '#edge/' + slugifyName(ordered[0]) + '--' + slugifyName(ordered[1]); 1299} 1300 1301// ===== END GLOBAL HELPERS ===== 1302 1303// Simple hash navigation - no promises to avoid Chrome extension errors 1304function handleInitialHash(retryCount = 0) { 1305 const MAX_RETRIES = 15; 1306 const RETRY_INTERVAL = 200; // Fast retry (200ms instead of 500ms) 1307 const rawHash = window.location.hash.substring(1); 1308 1309 if (__hashProcessed || !rawHash) { 1310 return; 1311 } 1312 1313 const networkOk = !!window.VIS_NETWORK; 1314 const nodesOk = !!nodes; 1315 const edgesOk = !!edges; 1316 const nodesCount = nodes ? nodes.get().length : 0; 1317 const edgesCount = edges ? edges.get().length : 0; 1318 const ready = networkOk && nodesOk && edgesOk && nodesCount > 0 && edgesCount > 0; 1319 1320 if (!ready && retryCount < MAX_RETRIES) { 1321 console.log(`ð Hash navigation retry ${retryCount + 1}/${MAX_RETRIES} - networkOk: ${networkOk}, nodesOk: ${nodesOk} (${nodesCount}), edgesOk: ${edgesOk} (${edgesCount})`); 1322 setTimeout(() => handleInitialHash(retryCount + 1), RETRY_INTERVAL); 1323 return; 1324 } 1325 if (!ready) { 1326 console.error("â Hash navigation FAILED - network not ready after retries"); 1327 __hashProcessed = true; 1328 return; 1329 } 1330 1331 __hashProcessed = true; 1332 1333 // Old format: #Node_Name 1334 if (!rawHash.includes('/')) { 1335 const decodedHash = decodeURIComponent(rawHash).replace(/_/g, ' '); 1336 let node = nodes.get().find(n => n.id === decodedHash || n.label === decodedHash); 1337 if (node) { 1338 setTimeout(() => { 1339 const pos = window.VIS_NETWORK.getPosition(node.id); 1340 window.VIS_NETWORK.moveTo({position: pos, scale: window.VIS_NETWORK.getScale(), animation: {duration: 500}}); 1341 window.VIS_NETWORK.selectNodes([node.id]); 1342 window.VIS_NETWORK.emit('click', {nodes: [node.id], edges: [], pointer: {DOM: {x: 0, y: 0}, canvas: {x: 0, y: 0}}}); 1343 }, 50); 1344 } 1345 return; 1346 } 1347 1348 const slashIndex = rawHash.indexOf('/'); 1349 const type = rawHash.substring(0, slashIndex); 1350 const value = rawHash.substring(slashIndex + 1); 1351 1352 if (type === 'node') { 1353 const decodedId = unslugifyName(value); 1354 const decodedNorm = normalizeForComparison(decodedId); 1355 let node = nodes.get().find(n => normalizeForComparison(n.id) === decodedNorm); 1356 1357 if (node) { 1358 setTimeout(() => { 1359 const pos = window.VIS_NETWORK.getPosition(node.id); 1360 window.VIS_NETWORK.moveTo({position: pos, scale: window.VIS_NETWORK.getScale(), animation: {duration: 500}}); 1361 window.VIS_NETWORK.selectNodes([node.id]); 1362 window.VIS_NETWORK.emit('click', {nodes: [node.id], edges: [], pointer: {DOM: {x: 0, y: 0}, canvas: {x: 0, y: 0}}}); 1363 }, 50); 1364 } 1365 return; 1366 } 1367 1368 if (type === 'edge') { 1369 const parts = value.split('--'); 1370 if (parts.length !== 2) return; 1371 1372 const left = unslugifyName(parts[0]); 1373 const right = unslugifyName(parts[1]); 1374 const leftNorm = normalizeForComparison(left); 1375 const rightNorm = normalizeForComparison(right); 1376 1377 let matchingEdge = edges.get().find(edge => { 1378 if (edge._isClusterEdge || edge.hidden) return false;
1379 const fromNode = nodes.get(edge.from); 1380 const toNode = nodes.get(edge.to); 1381 if (!fromNode || !toNode) return false; 1382 const pairNorm = [normalizeForComparison(fromNode.id), normalizeForComparison(toNode.id)].sort(); 1383 return pairNorm[0] === leftNorm && pairNorm[1] === rightNorm; 1384 }); 1385 1386 if (matchingEdge) { 1387 setTimeout(() => { 1388 const fromPos = window.VIS_NETWORK.getPosition(matchingEdge.from); 1389 const toPos = window.VIS_NETWORK.getPosition(matchingEdge.to); 1390 if (fromPos && toPos) { 1391 const centerX = (fromPos.x + toPos.x) / 2; 1392 const centerY = (fromPos.y + toPos.y) / 2; 1393 window.VIS_NETWORK.moveTo({position: {x: centerX, y: centerY}, scale: window.VIS_NETWORK.getScale(), animation: {duration: 500}}); 1394 } 1395 window.VIS_NETWORK.selectEdges([matchingEdge.id]); 1396 window.VIS_NETWORK.emit('click', {nodes: [], edges: [matchingEdge.id], pointer: {DOM: {x: 0, y: 0}, canvas: {x: 0, y: 0}}}); 1397 }, 50); 1398 } 1399 } 1400} 1401 1402document.addEventListener('DOMContentLoaded', async function () { 1403 applyUIStrings(); 1404 1405 if (CURRENT_LANG === 'es') { 1406 sortSelectOptionsAlphabetically('professionFilter'); 1407 } 1408 1409 const sel = document.getElementById('langSwitcher'); 1410 if (sel) sel.value = CURRENT_LANG; 1411 1412 // âââ Default snapshot del panel lateral (nodeInfo) 1413 let __defaultNodeInfoHTML = null; 1414 // Add this right after: document.addEventListener('DOMContentLoaded', async function () { 1415 try { 1416 // ADD THESE LINES: 1417 const preloadImages = (nodes) => { 1418 const promises = nodes 1419 .filter(node => node.image) 1420 .map(node => { 1421 return new Promise((resolve) => { 1422 const img = new Image(); 1423 img.src = node.image; 1424 img.onload = resolve; 1425 img.onerror = resolve; 1426 }); 1427 }); 1428 return Promise.all(promises); 1429 }; 1430 1431 // ð¥ DECLARAR nodePositions ANTES de usarlo 1432 let nodePositions = JSON.parse(localStorage.getItem('nodePositions') || '{}'); 1433 1434 // Load the network data 1435 // â NUEVO CÃDIGO (carga ambos archivos) 1436 const [networkResponse, positionsResponse] = await Promise.all([ 1437 fetch(getDataFileForLang(CURRENT_LANG)), 1438 fetch('positions_config.json') 1439 ]); 1440 1441 if (!networkResponse.ok) throw new Error('Error cargando datos de red'); 1442 const data = await networkResponse.json(); 1443 window.__GN_DATA = data; 1444 1445 // Cargar posiciones (si existe) 1446 if (positionsResponse.ok) { 1447 const positionsData = await positionsResponse.json(); 1448 nodePositions = positionsData; 1449 console.log('â Posiciones cargadas desde positions_config.json'); 1450 } else { 1451 console.warn('â ï¸ positions_config.json no encontrado, usando posiciones por defecto'); 1452 } 1453 1454 // Start image preloading 1455 // const imagePreload = preloadImages(data.nodes); 1456 1457 // Existing setup code 1458 const nodeInfo = document.getElementById('nodeInfo'); 1459 nodeInfo.style.maxHeight = '810px'; 1460 nodeInfo.style.overflowY = 'auto'; 1461 1462 const edgeCount = {};
1463 data.edges.forEach(edge => { 1464 edgeCount[edge.from] = (edgeCount[edge.from] || 0) + 1; 1465 edgeCount[edge.to] = (edgeCount[edge.to] || 0) + 1; 1466 }); 1467 1468 const nodesMap = {}; 1469 const labelToId = {}; 1470 // Cluster color palette 1471 const CLUSTER_COLORS = [ 1472 '#e57373', // red 1473 '#64b5f6', // blue 1474 '#81c784', // green 1475 '#ffd54f', // yellow 1476 '#ba68c8', // purple 1477 '#4db6ac', // teal 1478 '#ffb74d', // orange 1479 '#a1887f', // brown 1480 '#90a4ae', // gray-blue 1481 '#f06292', // pink 1482 ]; 1483 1484 const RADIAL_CLUSTERS = { 1485 "MADRAZO_FAMILY": { 1486 members: [ 1487 "Federico de Madrazo", 1488 "José de Madrazo", 1489 "Pedro de Madrazo", 1490 "Luis de Madrazo", 1491 "Raimundo de Madrazo", 1492 "Cecilia de Madrazo", 1493 "Román Garreta", 1494 "Mariano Fortuny y Madrazo", 1495 "Mariano Fortuny y Marsal", 1496 "Luisa Garreta", 1497 "Juan de Madrazo" 1498 ], 1499 radius: 200, 1500 padding: 150, 1501 centerYOffset: -150, 1502 startAngle: -Math.PI / 2, 1503 sharedBoundaryNodes: { 1504 "José de Madrazo": Math.PI * 1.25 // north-west, next to Federico 1505 }, 1506 title: "Madrazo family", 1507 titleEs: "Familia Madrazo", 1508 origin: "A prominent Spanish artistic dynasty deeply embedded in the institutional structure of nineteenth-century art production and curation. Beginning with José de Madrazo, the family maintained a sustained presence as court painters and museum administrators âmainly at the royal collections and future Prado Museumâ, closely connected to the Spanish monarchy across the reigns of Carlos IV, Fernando VII, and Isabel II. Their activity extended beyond Spain through strong professional and social ties in France and Italy, where they participated in collecting networks and transnational art markets. The family also played a significant role in the reception and circulation of Francisco de Goya's work: José de Madrazo was his court colleague, while later generationsâparticularly Federico de Madrazoâactively collected his works and facilitated their acquisition and sale in France in collaboration with figures such as Baron Taylor. The network includes both biological and marital relations, incorporating artists such as Mariano Fortuny y Marsal.", 1509 originEs: "Una destacada dinastÃa artÃstica española profundamente integrada en la estructura institucional de la producción y gestión del arte en el siglo XIX. Desde José de Madrazo, la familia mantuvo una presencia continuada como pintores de corte y responsables de instituciones museÃsticas âsobre todo las colecciones reales y el Museo del Pradoâ, estrechamente vinculados a la monarquÃa española durante los reinados de Carlos IV, Fernando VII e Isabel II. Su actividad se extendió más allá de España a través de fuertes vÃnculos profesionales y sociales en Francia e Italia, donde participaron en redes de coleccionismo y en el mercado artÃstico transnacional. La familia desempeñó también un papel relevante en la recepción y circulación de la obra de Francisco de Goya: José de Madrazo fue su colega en la corte, mientras que generaciones posteriores âen particular Federico de Madrazoâ coleccionaron su obra y facilitaron su compra y venta en Francia en colaboración con figuras como el Baron Taylor. El grupo incluye tanto relaciones biológicas como matrimoniales, incorporando artistas como Mariano Fortuny y Marsal.", 1510 bibliography: [ 1511 "Amaya Alzaga, <em>Raimundo de Madrazo y Garreta (1841-1920)</em>, PhD thesis, UNED, 2012", 1512 "Amaya Alzaga, 'Federico de Madrazo en ParÃs (1837-1839): su mirada sobre la pintura francesa de su tiempo y viceversa', in <em>Federico de Madrazo y Carlos Luis de Ribera. Pintores Del Romanticismo Español</em>, 2018, pp.8-28.", 1513 "José Luis DÃez, <em>Federico de Madrazo: epistolario, vol. 1</em>, 1994.", 1514 "Ainhoa Gilarranz Ibáñez, 'No Solo Es Arte: Las Redes de Federico de Madrazo Durante Su Pensionado En ParÃs y Roma (1837-1842)'', <em>Bulletin Hispanique</em>, no. 122-1, June 2020, pp. 277-92.", 1515 "Carlos González López, <em>Federico de Madrazo y Küntz</em>, 1981.", 1516 "Carlos González López, 'Federico de Madrazo en el ParÃs de 1833', <em>Archivo Español de Arte</em>, 1994, vol. 67, no. 267, pp.211-24.", 1517 "Carlos González Navarro, <em>José de Madrazo (1781-1859), Dibujante</em>, PhD thesis, Madrid, Universidad Complutense de Madrid, 2023.", 1518 "Monte MartÃ, <em>El mundo de los Madrazo: colección de la Comunidad de Madrid</em>, 2007.", 1519 "Pedro J. MartÃnez Plaza, <em>Epistolario del Archivo de Madrazo del Museo del Prado. Cartas de Mariano Fortuny, Cecilia, Ricardo, Raimundo e Isabel de Madrazo</em>, 2017.", 1520 "Raquel Sánchez, 'José de Madrazo y los deberes del padre perfecto', in <em>Hijos del Siglo</em>, 2024." 1521 ], 1522 websites: [] 1523 // Format: websites: ["[Label Name](https://url.com)", "[Another](https://url2.com)"] 1524 }, 1525 1526 "HUGO_CENACLE": { 1527 members: [ 1528 "Honoré de Balzac", 1529 "Louis Boulanger", 1530 "Eugène Delacroix", 1531 "Achille Devéria", 1532 "Alexandre Dumas père", 1533 "Antoine Fontaney",
1534 "Théophile Gautier", 1535 "Prosper Mérimée", 1536 "Alfred de Musset", 1537 "Charles Nodier", 1538 "Charles-Augustin Sainte-Beuve" 1539 ], 1540 center: "Victor Hugo", 1541 radius: 200, 1542 padding: 90, 1543 startAngle: -Math.PI / 2, 1544 title: "Hugo cenacle", 1545 titleEs: "Clúster Hugo", 1546 origin: "A literary and artistic circle formed around Victor Hugo in the late 1820s, often associated with the emergence of French Romanticism. Composed primarily of young writers, poets, and playwrights, alongside painters and draftsmen, the group developed in opposition to established academic and classical norms, advocating new artistic and literary forms aligned with contemporary political liberalism and the cultural ideal of modernity. Many members were also active collectors and critics. Within their broader Romantic interests, they showed an early and sustained engagement with Francisco de Goyaâparticularly his <i>Caprichos</i>âbecoming among the first French intellectuals to systematically interpret and circulate his imagery, well before its reception in Spain, and contributing to his later critical canonization.", 1547 originEs: "Un cÃrculo literario y artÃstico formado en torno a Victor Hugo a finales de la década de 1820, asociado a la emergencia del Romanticismo francés. Integrado principalmente por jóvenes escritores, poetas y dramaturgos, junto a pintores y dibujantes, el grupo se configuró en oposición a las normas académicas y clásicas establecidas, defendiendo nuevas formas artÃsticas y literarias vinculadas al liberalismo polÃtico contemporáneo y a un ideal cultural de modernidad. Muchos de sus miembros fueron también coleccionistas y crÃticos. Dentro de sus intereses románticos más amplios, mostraron una temprana y sostenida atención a Francisco de Goya âen particular los <i>Caprichos</i>â, convirtiéndose en algunos de los primeros intelectuales franceses en interpretar y difundir sistemáticamente su imaginerÃa, mucho antes de su recepción en España, contribuyendo a su posterior canonización crÃtica.", 1548 bibliography: [ 1549 "Claude Schopp (ed.), <em>Une amitié capitale: Correspondance Victor Hugo - Alexandre Dumas</em>, 2015.", 1550 "Claude Schopp (ed.), <em>Frères d'armes de la révolution romantique. Lettres d'Alexandre Dumas au Baron Taylor et à Adrien Dauzats</em>, Cahiers Alexandre Dumas, 1993, no. 20 (reedited in 2023).", 1551 "Robert Avrett, 'Ochoa's Translation of Hugo's Hernani: A Study in Racial Psychologies' <em>The Modern Language Journal</em>, Vol. 38 no. 7, Nov 1954, pp.362-65.", 1552 "Ilse Hempel Lipschutz, <em>Spanish Painting and the French Romantics</em>, Harvard University Press, 1972.", 1553 "Mª Rosario Ozaeta, 'Eugenio de Ochoa, Traductor de Hugo', in <em>Neoclásicos y románticos ante la traducción</em>, edited by Francisco Lafarga et al, Universidad de Murcia, 2002, pp.419-36.", 1554 "Roberto Dengler Gassin, 'Algunas consideraciones a propósito de Hernani, drama de Victor Hugo (1830), versión castellana de Eugenio de Ochoa (1836)', in ed. Donaire & Lafarga, <em>Traducción y adaptación cultural: España-Francia</em>, 1991, pp.337-345.", 1555 "Anne Martin-Fugier, <i>Les Romantiques: 1820-1848</i>, 1998.", 1556 "René Bray, <i>Chronologie du romantisme (1804-1830)</i>, t.33, coll. Bibliothèque de la Revue des cours et conférences, VII, 1932.", 1557 "Charles Marc Des Granges, <i>Le Romantisme et la critique : la presse littéraire sous la restauration, 1815-1830</i>, 1907.", 1558 "Edmond Biré, <i>Victor Hugo avant 1830</i>, 1902." 1559 ], 1560 websites: [ 1561 "[Atenas. Diario de a bordo](https://atenas-diariodeabordo.blogspot.com/search/label/Romanticismo%20y%20Rom%C3%A1nticos%20%E2%80%93III%20a%E2%80%93%20V%C3%8DCTOR%20HUGO.%20La%20vida): Romanticismo y Románticos -III a- VÃctor Hugo. La vida.", 1562 ] 1563 // Format: websites: ["[Label Name](https://url.com)", "[Another](https://url2.com)"] 1564 }, 1565 1566 "ILUSTRADOS_CLUSTER": { 1567 members: [ 1568 "XI Count of Altamira", 1569 "Francisco Cabarrús", 1570 "Juan AgustÃn Ceán Bermúdez", 1571 "Leandro Fernández de MoratÃn", 1572 "JoaquÃn MarÃa Ferrer", 1573 "Count of Floridablanca", 1574 "Francisco de Goya", 1575 "Bernardo de Iriarte", 1576 "Juan Antonio Llorente", 1577 "Sebastián MartÃnez y Pérez", 1578 "Gaspar Melchor de Jovellanos",
1579 "Juan Meléndez Valdés", 1580 "MartÃn Zapater", 1581 "Juan Fernández de Rojas", 1582 "Manuel GarcÃa de la Prada" 1583 ], 1584 radius: 166, 1585 padding: 92, 1586 centerYOffset: 180, 1587 startAngle: -Math.PI / 2, 1588 sharedBoundaryNodes: { 1589 "Francisco de Goya": Math.PI / 2, // south of cluster 1590 "MartÃn Zapater": Math.PI / 2.2 // also south, close to Goya 1591 }, 1592 title: "Ilustrados cluster", 1593 titleEs: "Clúster Ilustrados", 1594 origin: "A heterogeneous intellectual and political network associated with the Spanish Enlightenment (<i>Ilustración</i>), united by reformist ideals and a shared commitment to cultural, administrative, and social modernization. Comprising statesmen, writers, clerics, bankers, and artists, the group contributed to debates on progress and institutional reform in late eighteenth- and early nineteenth-century Spain, often operating within overlapping courtly, academic, and economic spheres. Many members maintained close personal and professional relationships with Francisco de Goya, who portrayed several of them in portraiture, reflecting both patterns of patronage and a degree of intellectual affinity.", 1595 originEs: "Un entramado intelectual y polÃtico heterogéneo vinculado a la Ilustración española, unido por ideales reformistas y por un compromiso compartido con la modernización cultural, administrativa y social. Integrado por estadistas, escritores, clérigos, banqueros y artistas, el grupo participó en los debates sobre el progreso y la reforma institucional en la España de finales del siglo XVIII y comienzos del XIX, operando frecuentemente en espacios cortesanos, académicos y económicos interconectados. Muchos de sus miembros mantuvieron relaciones estrechas personales y profesionales con Francisco de Goya, quien retrató a varios de ellos, reflejando tanto dinámicas de mecenazgo como un grado de afinidad intelectual.", 1596 bibliography: [ 1597 "Mercedes Ãgueda Villar & Xavier de Salas (eds.), <i>Francisco de Goya: cartas a MartÃn Zapater</i>, 1982.", 1598 "Ãngel Canellas López, <i>Diplomatario. Francisco de Goya</i>, 1981.", 1599 "Jacqueline Hara, <i>Francisco de Goya (1746-1828). Letters of Love and Friendship in Translation</i>, 1997.", 1600 "Enrique Lafuente Ferrari, 'Las cartas de Goya a Zapater y los epistolarios españoles', <i>Homenaje a la memoria de don Antonio RodrÃguez Moñino, 1910-1970</i>, 1975, pp. 285-328.", 1601 "Luis MartÃn-Estudillo, <i>Goya o el misterio de la lectura</i>, 2023.", 1602 "Manuela B. Mena Marqués, Gudrun Maurer (coord.), Virginia Albarrán MartÃn, <i>Goya y la corte ilustrada</i>, 2017.", 1603 "Manuela B. Mena Marqués, 'Jovellanos pintado por Goya', <i>BoletÃn jovellanista</i>, no.17, 2017, pp.135-47.", 1604 "José Luis Morales y MarÃn, <i>Francisco Bayeu. Vida y obra</i>, 1995.", 1605 "Natacha Seseña, <i>Goya y las mujeres</i>, 2004.", 1606 "Janis Tomlinson, <em>Goya in the Twilight of Enlightenment</em>, 1992." 1607 ], 1608 websites: [ 1609 "[Cartas de Goya a MartÃn Zapater](https://www.museodelprado.es/aprende/enciclopedia/voz/cartas-de-goya-a-martin-zapater/2ab3aedb-07a9-4031-b6e0-64d9806ac8b5), Mercedes Ãgueda Villar, Museo del Prado." 1610 ], 1611 conferences: [ 1612 "Manuela B. Mena Marqués, [Goya y sus amigos](https://www.museodelprado.es/actualidad/multimedia/goya-y-sus-amigos-por-manuela-mena/2bcc3d56-1056-43cb-80d2-ccb552b54541), Cátedra 2013." 1613 ] 1614 }, 1615 1616 "GOYA_FAMILY": { 1617 members: [ 1618 "Francisco de Goya", 1619 "Javier Goya", 1620 "Mariano Goya", 1621 "Gumersinda Goicoechea", 1622 "Josefa Bayeu", 1623 "Francisco Bayeu" 1624 ], 1625 radius: 85, 1626 padding: 65, 1627 startAngle: -Math.PI / 2, 1628 sharedBoundaryNodes: { 1629 "Francisco de Goya": -Math.PI / 2 1630 }, 1631 title: "Goya family", 1632 titleEs: "Familia Goya", 1633 origin: "A family network of Francisco de Goya, encompassing his biological relatives and close in-laws. Originating in Aragón and later established in Madrid, the group reflects the painterâs early social and professional environment as well as the domestic ties that connected him to the Bayeu family, particularly through marriage alliances and artistic apprenticeship networks.", 1634 originEs: "Un entramado familiar en torno a Francisco de Goya, que incluye tanto sus parientes biológicos como sus allegados polÃticos. De origen aragonés y posteriormente establecido en Madrid, el grupo refleja el entorno social y profesional temprano del pintor, asà como los vÃnculos domésticos que lo conectaron con la fam
1634ilia Bayeu, especialmente a través de alianzas matrimoniales y redes de formación artÃstica.", 1635 bibliography: [], 1636 websites: [] 1637 }, 1638 1639 "OSUNA_CORE": { 1640 center: "XII Countess-Duchess of Benavente and Duchess of Osuna", 1641 members: [ 1642 "IX Duke of Osuna", 1643 "X Duke of Osuna", 1644 "VIII Duchess of Abrantes", 1645 "X Marchioness of Santa Cruz" 1646 ], 1647 radius: 135, 1648 startAngle: -Math.PI / 2, 1649 title: "Osuna core", 1650 titleEs: "Núcleo Osuna", 1651 origin: "A leading Spanish aristocratic circle of the late eighteenth and early nineteenth centuries, centered on the House of Osuna and its allied networks. One of the most important patrons and collectors of Francisco de Goya, the group supported a wide range of his production, from decorative commissions to more experimental and critical imagery, including witchcraft scenes and the Caprichos series. Their cultural profile combined Enlightenment-inflected interests with strong engagement in artistic patronage, and included salonnières who facilitated intellectual and social exchange within elite Madrid circles.", 1652 originEs: "Un influyente cÃrculo de la aristocracia española de finales del siglo XVIII y comienzos del XIX, centrado en la casa de Osuna y sus redes aliadas. Uno de los principales mecenas y coleccionistas de Francisco de Goya, el grupo impulsó un amplio espectro de su producción, desde encargos decorativos hasta imágenes más experimentales y crÃticas, incluyendo escenas de brujerÃa y la serie de los Caprichos. Su perfil cultural combinaba intereses de raÃz ilustrada con un fuerte compromiso con el mecenazgo artÃstico, e incluÃa salonnières que facilitaron el intercambio intelectual y social en los cÃrculos de élite madrileños.", 1653 bibliography: [ 1654 "Manuela B. Mena Marqués, 'Dos adquisiciones de Goya en el Museo del Prado: <i>Vuelo de brujas</i> (1999) y <i>La marquesa de Santa Cruz</i> (1986), in <i>Los tesoros ocultos del Museo del Prado</i>, 2017, pp.277-305.", 1655 "Manuela B. Mena Marqués, Gudrun Maurer (coord.), Virginia Albarrán MartÃn, <i>Goya y la corte ilustrada</i>, 2017." 1656 ], 1657 websites: [], 1658 conferences: [ 1659 "Manuela Mena, [Vuelo de brujas y La Marquesa de Santa Cruz. Francisco de Goya](https://fundacionbarrie.org/conferencia-manuela-mena), 2016." 1660 ] 1661 }, 1662 1663 "MONTIJO_CORE": { 1664 center: "Eugenio Eulalio Palafox, VII Count of Montijo", 1665 members: [ 1666 "MarÃa Francisca de Sales Portocarrero, VI Countess of Montijo", 1667 "Eugenio Eulalio Palafox, VII Count of Montijo", 1668 "Cipriano Portocarrero, VIII Count of Montijo", 1669 "MarÃa Manuela Kirkpatrick", 1670 "Eugenia de Montijo", 1671 "MarÃa Gabriela de Palafox, Marchioness of Lazán", 1672 "MarÃa Tomasa Palafox, Marchioness of Villafranca" 1673 ], 1674 radius: 140, 1675 padding: 100, 1676 startAngle: -Math.PI / 2, 1677 title: "Montijo core", 1678 titleEs: "Núcleo Montijo", 1679 origin: "A dynastic and social network of Spanish nobility centered on the Montijo and allied PalafoxâVillafranca families, operating across Spain and France in the late eighteenth and nineteenth centuries. United through marriage alliances, courtly mobility, and shared cultural interests, they played a role in the patronage and circulation of Francisco de Goyaâs work. Several members functioned as salonnières and cultural intermediaries in transnational elite circles, particularly in Paris, where intellectual and literary exchanges included figures such as Prosper Mérimée. The groupâs later consolidation of influence at the French imperial court further extended its cultural and political reach.", 1680 originEs: "Un entramado dinástico y social de la nobleza española centrado en las casas de Montijo y las familias aliadas Palafox-Villafranca, activo entre finales del siglo XVIII y el XIX entre España y Francia. Unidos por alianzas matrimoniales, movilidad cortesana e intereses culturales compartidos, participaron en el patronazgo y circulación de la obra de Francisco de Goya. Varios de sus miembros actuaron como salonnières e intermediarios culturales en cÃrculos de élite transnacionales, especialmente en ParÃs, donde mantuvieron contactos con figuras literarias e intelectuales como Prosper Mérimée. La posterior proyección de la familia en la corte imperial francesa amplió su influencia cultural y polÃtica.", 1681 bibliography: [], 1682 websites: [] 1683 }, 1684
1685 "SPANISH_COURT_PAINTERS": { 1686 members: [ 1687 "Francisco de Goya", 1688 "Francisco Bayeu", 1689 "Mariano Salvador Maella", 1690 "Giambattista Tiepolo", 1691 "Giandomenico Tiepolo", 1692 "Vicente López", 1693 "Anton Raphael Mengs", 1694 "AgustÃn Esteve", 1695 "Federico de Madrazo", 1696 "José de Madrazo" 1697 ], 1698 radius: 160, // increased from 132 for wider circle 1699 padding: 92, 1700 startAngle: -Math.PI / 2, 1701 title: "Spanish court painters", 1702 titleEs: "Pintores de la corte española", 1703 origin: "A heterogeneous group of painters who served the Spanish royal court across the late eighteenth and early nineteenth centuries. Despite differing stylistic orientations and artistic backgrounds, they were connected through their roles as court painters, resulting in professional proximity, occasional collaboration, and shifting degrees of personal and artistic affinity. Within this network, family ties and generational succession also played a role in shaping interactions, while stylistic contrastsâfrom late Baroque tradition to Neoclassicism and Romanticismâcoexisted within the same institutional framework.", 1704 originEs: "Un grupo heterogéneo de pintores al servicio de la corte española a finales del siglo XVIII y comienzos del XIX. A pesar de sus diferentes estilos, orÃgenes y posiciones estéticas, estuvieron vinculados por su condición de pintores de cámara, lo que generó proximidad profesional, colaboración ocasional y distintos grados de afinidad personal y artÃstica. Dentro de esta red, los lazos familiares y la sucesión generacional también influyeron en las relaciones, mientras coexistÃan orientaciones estilÃsticas contrastadas âdesde la tradición tardobarroca hasta el neoclasicismo y el romanticismoâ dentro del mismo marco institucional.", 1705 bibliography: [ 1706 "Ester Alba Pagán, 'Imagen y poder. La pintura valenciana en la época de Fernando VII (1814-1833)de la alegorÃa a la pintura conmemorativa', <i>Arte, poder y sociedad en la España de los siglos XV a XX</i>, 2008, pp.337-54.", 1707 "Virginia Albarrán MartÃn, 'Pintura de tema religioso de AgustÃn Esteve', in Alejandro Cañestro Donoso, ed. <em>Scripta artium in honorem prof. José Manuel Cruz Valdovinos</em>, 1, Madrid, pp. 297-318.", 1708 "Virginia Albarrán MartÃn, 'De Goyas que son Esteves', in <em>Goya: Collecting the Grotesque</em>, upcoming.", 1709 "Manuela B. Mena Marqués, Gudrun Maurer (coord.), Virginia Albarrán MartÃn, <i>Goya y la corte ilustrada</i>, 2017.", 1710 "Jesusa Vega, 'Fernando VII: resistencia y deseo', <i>Journal of Spanish Cultural Studies</i>, 2013, vol.14, no.4, pp.348-99." 1711 ], 1712 websites: [ 1713 "[Colección de Carlos III: José Manuel de la Mano, Museo del Prado](https://www.museodelprado.es/aprende/enciclopedia/voz/coleccion-de-carlos-iii/4d51e705-b17d-4836-8745-54569c9e7b30).", 1714 "[Descubrir el Arte: El poderoso legado de Carlos III](https://www.descubrirelarte.es/2016/12/30/el-poderoso-legado-de-carlos-iii.html).", 1715 "[Colección de Carlos IV: Luis Miguel Enciso Recio, Museo del Prado](https://www.museodelprado.es/aprende/enciclopedia/voz/coleccion-de-carlos-iv/f1094cbd-7bfb-4b99-97fa-04769b0a3954).", 1716 "[Más de Arte: Carlos IV, mecenas y coleccionista](https://masdearte.com/carlos-iv-mecenas-y-coleccionista/).", 1717 "[Carlos IV, por José de Madrazo](https://www.galeriadelascoleccionesreales.es/obra-de-arte/carlos-iv/726318d0-2f71-4c14-9394-681e52e2a480), GalerÃa de las Colecciones Reales.", 1718 "[Colección de Fernando VII: Ãngel Aterido, Museo del Prado](https://www.museodelprado.es/aprende/enciclopedia/voz/coleccion-de-fernando-vii/1fd09a07-e3e0-4bca-945f-1506b871fc2b)." 1719 ], 1720 conferences: [ 1721 "[<i>El arte en tiempo de Carlos III, IV Jornadas de Arte</i>](https://dialnet.unirioja.es/servlet/libro?codigo=509232), 1989.", 1722 "[<i>Carlos IV y las artes. El rey coleccionista</i>](https://www.patrimonionacional.es/sites/default/files/carlos_iv_y_las_artes.pdf), Universidad Complutense / Patrimonio Nacional, 2019." 1723 ] 1724 }, 1725 1726 "PRINT_SPECIALISTS": { 1727 members: [ 1728 "Eugène Piot", 1729 "Philippe Burty", 1730 "Paul Lefort", 1731 "Charles Blanc", 1732 "Paul Mantz", 1733 "Jules Jacquemart", 1734 "Alphonse Hirsch" 1735 ], 1736 radius: 100,
1737 padding: 80, 1738 startAngle: -Math.PI / 2, 1739 title: "Print specialists", 1740 titleEs: "Especialistas en estampa", 1741 origin: "A group of French printmakers, editors, critics, and collectors who played a decisive role in the nineteenth-century reception of Francisco de Goya. Active from the 1830s onwards, they were among the earliest scholarly interpreters of his graphic work, combining connoisseurship, collecting practices, and art criticism. Their writings and collections contributed to the first systematic studies and cataloguing efforts devoted to Goya's oeuvre.", 1742 originEs: "Un grupo de grabadores, editores, crÃticos y coleccionistas franceses que desempeñaron un papel decisivo en la recepción de Francisco de Goya en el siglo XIX. Activos desde la década de 1830, fueron algunos de los primeros intérpretes eruditos de su obra gráfica, combinando el conocimiento experto, el coleccionismo y la crÃtica de arte. Sus escritos y colecciones contribuyeron a los primeros intentos sistemáticos de estudio y catalogación de la obra de Goya.", 1743 bibliography: [], 1744 websites: [] 1745 }, 1746 1747 "MADRAZO_CARDERERA_GROUP": { 1748 members: [ 1749 "Federico de Madrazo", 1750 "Pedro de Madrazo", 1751 "ValentÃn Carderera", 1752 "Eugenio Ochoa", 1753 "Santiago Masarnau", 1754 "Vicente Masarnau", 1755 "Carlos Luis de Ribera" 1756 ], 1757 radius: 135, 1758 padding: 84, 1759 centerYOffset: -280, 1760 startAngle: -Math.PI / 2, 1761 sharedBoundaryNodes: { 1762 "Federico de Madrazo": Math.PI / 2, 1763 "Pedro de Madrazo": Math.PI / 2 1764 }, 1765 title: "Madrazo-Carderera group", 1766 titleEs: "Grupo Madrazo-Carderera", 1767 origin: "A group of Spanish artists, writers, and intellectuals of the same generation, linked by long-standing friendships formed in youth and sustained through shared artistic and cultural interests. Including members of the Madrazo family, they maintained close connections with Parisian cultural circles, where several of them lived and engaged with French Romanticism. They also played an important role in the dissemination and critical reception of Francisco de Goya's work in both Spain and France.", 1768 originEs: "Un grupo de artistas, escritores e intelectuales españoles de la misma generación, unidos por amistades de larga duración formadas en la juventud y mantenidas a través de intereses artÃsticos y culturales compartidos. Incluye miembros de la familia Madrazo y mantiene vÃnculos estrechos con los cÃrculos culturales parisinos, donde varios de ellos residieron y entraron en contacto con el Romanticismo francés. También desempeñaron un papel importante en la difusión y recepción crÃtica de la obra de Francisco de Goya tanto en España como en Francia.", 1769 bibliography: [ 1770 "Amaya Alzaga, 'Federico de Madrazo en ParÃs (1837-1839): su mirada sobre la pintura francesa de su tiempo y viceversa', in <em>Federico de Madrazo y Carlos Luis de Ribera. Pintores Del Romanticismo Español</em>, 2018, pp.8-28.", 1771 "Robert Avrett, 'Ochoa's Translation of Hugo's Hernani: A Study in Racial Psychologies' <em>The Modern Language Journal</em>, Vol. 38 no. 7, Nov 1954, pp.362-65.", 1772 "Paula Fayos Pérez, 'ValentÃn Carderera and the Dissemination of Goya's Graphic Work in France', <em>The Burlington Magazine</em>, vol. 162, no. 1413, 2020, pp. 1048-55.", 1773 "José MarÃa Lanzarote Guiral, <em>Diarios de viaje de ValentÃn Carderera por Europa (1841-1861). ParÃs, Londres, Bélgica y Alemania</em>, 2016.", 1774 "Eugenio de Ochoa, <em>Paris, Londres y Madrid, Paris</em>, Dramard Baudry, 1861.", 1775 "Mª Rosario Ozaeta, 'Eugenio de Ochoa, Traductor de Hugo', in <em>Neoclásicos y románticos ante la traducción</em>, edited by Francisco Lafarga et al, Universidad de Murcia, 2002, pp.419-36.", 1776 "Roberto Dengler Gassin, 'Algunas consideraciones a propósito de Hernani, drama de Victor Hugo (1830), versión castellana de Eugenio de Ochoa (1836)', in ed. Donaire & Lafarga, <em>Traducción y adaptación cultural: España-Francia</em>, 1991, pp.337-345." 1777 ], 1778 websites: [] 1779 }, 1780 1781 "BOURBON_CORE": { 1782 members: [ 1783 "Carlos III", 1784 "Carlos IV", 1785 "MarÃa Luisa de Parma", 1786 "Fernando VII", 1787 "Infanta Luisa Fernanda de Borbón", 1788 "Luis de Borbón", 1789 "MarÃa Teresa de Vallabriga", 1790 "XV Countess of Chinchón", 1791 "Isabel II", 1792 "MarÃa Cristina de Borbón-Dos Sicilias" 1793 ], 1794 radius: 150, 1795 padding: 120, 1796 startAngle: -Math.PI / 2, 1797 sharedBoundaryNodes: { 1798 "XV Countess of Chinchón": 0 1799 }, 1800 title: "Bourbon cluster", 1801 titleEs: "Clúster Borbón", 1802 origin: "A core group of Spanish Bourbon royalty spanning the late eighteenth and nineteenth centuries. Centered on the royal family of Carlos III and his descendants, this network includes successive monarchs and consorts who shaped court politics, patronage systems, and the commissioning and circulation of Francisco de Goya's work within royal and dynastic contexts.", 1803 originEs: "Un núcleo de la realeza borbónica española que abarca finales del siglo XVIII y el siglo XIX. Centrado en la familia de Carlos III y sus descendientes, este grupo incluye a monarcas y consortes sucesivos que configuraron la polÃtica de corte, los sistemas de patronazgo y el encargo y circulación de la obra de Francisco de Goya en contextos dinásticos y cortesanos.", 1804 bibliography: [ 1805 "Manuela B. Mena Marqués, <i>Teresa de Vallabriga, infanta de España: Francisco de Goya y la familia Borbón-Vallabriga</i>, Magdalena Lasala (coord.), 2018.", 1806 "Manuela B. Mena Marqués, 'El Retrato de Don Luis MarÃa de Borbón y Vallabriga niño, estudiando geografÃa', <i>Luis MarÃa de Borbón. Francisco de Goya</i>, 2007, pp.22-42." 1807 ], 1808 websites: [] 1809 }, 1810 1811 "VILLAFRANCA_CLUSTER": { 1812 members: [ 1813 "Francisco Ãlvarez de Toledo, XII Marquis of Villafranca", 1814 "MarÃa Antonia Gonzaga, Marchioness of Villafranca (widow)", 1815 "MarÃa Tomasa Palafox, Marchioness of Villafranca", 1816 "José Ãlvarez de Toledo, Duke of Alba", 1817 "MarÃa Teresa de Silva, XIII Duchess of Alba" 1818 ], 1819 radius: 78, 1820 padding: 84, 1821 startAngle: -Math.PI / 2, 1822 title: "Villafranca-Alba cluster", 1823 titleEs: "Clúster Villafranca-Alba", 1824 origin: "A group of Spanish aristocrats connected through the House of Villafranca and the House of Alba. Linked by family ties, marriage alliances, and noble inheritance, they formed part of one of the most influential aristocratic networks in Spain during the late eighteenth and early nineteenth centuries, and were significant patrons and collectors of Francisco de Goya.", 1825 originEs: "Un grupo de aristócratas españoles vinculados a las casas de Villafranca y Alba. Unidos por lazos familiares, alianzas matrimoniales y herencias nobiliarias, formaron parte de una de las redes aristocráticas más influyentes de España a finales del siglo XVIII y comienzos del XIX, y fueron importantes mecen
1825as y coleccionistas de Francisco de Goya.", 1826 bibliography: [ 1827 "Manuela B. Mena Marqués & Gudrun Maurer, <i>La duquesa de Alba, musa de Goya: el mito y la historia</i>, 2006." 1828 ], 1829 websites: [] 1830 }, 1831 1832 "TAYLOR_CLUSTER": { 1833 members: [ 1834 "Baron Taylor", 1835 "Adrien Dauzats", 1836 "Pharamond Blanchard" 1837 ], 1838 radius: 78, 1839 padding: 84, 1840 startAngle: -Math.PI / 2, 1841 title: "Taylor cluster", 1842 titleEs: "Clúster Taylor", 1843 origin: "A group of artists and cultural intermediaries closely associated with the French discovery of Spain during the nineteenth century. Through their travels, artistic collaborations, and collecting activities, they helped shape French perceptions of Spanish culture and played a significant role in the acquisition of Spanish artworks for Louis Philippe I's Galerie espagnole, maintaining connections with figures such as Javier Goya and the Madrazo family.", 1844 originEs: "Un grupo de artistas, viajeros e intermediarios culturales que contribuyeron al creciente interés francés por España durante el siglo XIX. Estrechamente implicados en la formación de la Galerie espagnole de Louis Philippe I, promovieron el arte y la cultura españoles a través de los viajes, el coleccionismo y el intercambio artÃstico, al tiempo que mantuvieron importantes vÃnculos con Javier Goya y la familia Madrazo.", 1845 bibliography: [ 1846 "Claude Schopp (ed.), <em>Frères d'armes de la révolution romantique. Lettres d'Alexandre Dumas au Baron Taylor et à Adrien Dauzats</em>, Cahiers Alexandre Dumas, 1993, no. 20 (reedited in 2023).", 1847 "Paul Guinard, <em>Dauzats et Blanchard: Peintres de l'Espagne Romantique</em>, 1967.", 1848 "Ilse Hempel Lipschutz, <em>Spanish Painting and the French Romantics</em>, Harvard University Press, 1972.", 1849 "Alisa Luxenberg, <em>The Galerie Espagnole and the Museo Nacional, 1835-1853: Saving Spanish Art, or the Politics of Patrimony</em>, 2008.", 1850 "Gary Tinterow et al. <em>Manet/Velázquez: The French Taste for Spanish Painting</em>, New York, Metropolitan Museum of Art, 2003." 1851 ], 1852 websites: [] 1853 }, 1854 1855 "DAVID_CLUSTER": { 1856 members: [ 1857 "Jacques-Louis David", 1858 "Jean-Auguste-Dominique Ingres", 1859 "Antoine-Jean Gros", 1860 "Paul Delaroche" 1861 ], 1862 radius: 78, 1863 padding: 84, 1864 startAngle: -Math.PI / 2, 1865 title: "David cluster", 1866 titleEs: "Clúster David", 1867 origin: "A group of painters linked to the artistic legacy of Jacques-Louis David, whose influence extended from Neoclassicism to the broader academic tradition of the nineteenth century. Through their historical paintings, classical ideals, and teaching networks, they played a central role in shaping European academic art.", 1868 originEs: "Un grupo de pintores vinculados al legado artÃstico de Jacques-Louis David, cuya influencia se extendió desde el Neoclasicismo hasta la tradición académica más amplia del siglo XIX. A través de sus pinturas de historia, ideales clásicos y redes de enseñanza, desempeñaron un papel central en la configuración del arte académico europeo.", 1869 bibliography: [], 1870 websites: [] 1871 }, 1872 1873 "IMPRESSIONIST_CLUSTER": { 1874 members: [ 1875 "Félix Bracquemond", 1876 "Edgar Degas", 1877 "Henri Fantin-Latour", 1878 "Ãdouard Manet", 1879 "Berthe Morisot", 1880 "Auguste Renoir" 1881 ], 1882 radius: 78, 1883 padding: 84, 1884 startAngle: -Math.PI / 2, 1885 title: "Impressionist cluster", 1886 titleEs: "Clúster Impresionista", 1887 origin: "French artistic movement originating in the 1860s, dedicated to capturing the ephemeral light of a specific instant and its effect on colors and forms. Each artist expressed their own subjective vision, resulting in diverse and highly individual styles rather than a unified aesthetic. The movement was formally introduced through the first independent exhibition held in Paris in 1874.", 1888 originEs: "Movimiento artÃstico francés que se originó en la década de 1860, dedicado a capturar la luz efÃmera de un instante especÃfico y su efecto en los colores y las formas. Cada artista expresó su propia visión subjetiva, resultando en estilos diversos e individuales más que en una estética unificada. El movimiento fue presentado formalmente a través de la primera exposición independiente celebrada en ParÃs en 1874.", 1889 bibliography: [ 1890 "Françoise Cachin, Charles S. Moffett & Juliet Wilson-Bareau, <em>Manet: 1832-1883</em>, Paris, Réunion des Musées Nationaux, 1983.", 1891 "Dorothee Hansen, <em>Manet and Astruc. Friendship and Inspiration</em>, CEEH, 2021.", 1892 "Sophie Monneret, <em>L'Impressionnisme et son époque</em>, vol.2, t.1, Paris, Robert Laffont, 1987.", 1893 "Adolphe Tabarant, <em>Manet et ses Åuvres</em>, Paris, Gallimard, 1947 (1st ed. 1897).", 1894 "Gary Tinterow et al. <em>Manet/Velázquez: The French Taste for Spanish Painting</em>, New York, Metropolitan Museum of Art, 2003.", 1895 "Juliet Wilson-Bareau, <em>Manet: The Execution of Maximilian: Painting, Politics and Censorship</em>, Princeton (New Jersey), Princeton University Press, 1992.", 1896 "Stephan Wolohojian and Ashley Dunn, <em>Manet/Degas</em>, The Metropolitan Museum of Art, 2023." 1897 ], 1898 websites: [ 1899 "[Networks of Impressionism](https://www.navigating.art/articles-from-navigatingart/networks-of-impressionism): An upcoming collaborative digital endeavor from the Museum Barberini (Postdam, Germany), the Wildenstein Plattner Institute and Navigating.art." 1900 ] 1901 }, 1902 1903 "SPANISH_ROMANTICS_CLUSTER": { 1904 members: [ 1905 "José de Espronceda", 1906 "Mariano José Larra", 1907 "José Zorrilla", 1908 "Jenaro Pérez Villaamil", 1909 "Ramón de Mesonero Romanos", 1910 "Carlos Luis de Ribera", 1911 "Eugenio Lucas Velázquez" 1912 ], 1913 radius: 100,
1914 padding: 90, 1915 startAngle: -Math.PI / 2, 1916 title: "Spanish Romantics", 1917 titleEs: "Románticos españoles", 1918 origin: "A heterogeneous group of Spanish writers, painters, and critics of the same generation, united by shared aesthetic and political sensibilities representing key figures in Spain's Romantic movement of the 19th century. Encompassing both literary figures and visual artists, they were committed to expressive, emotional forms and liberal ideals. Many maintained connections to Parisian Romantic circles and contributed to Spain's cultural modernization through art, theater, literature, and social commentary.", 1919 originEs: "Un grupo heterogéneo de escritores, pintores y crÃticos españoles de la misma generación, unidos por una sensibilidad estética y polÃtica común, figuras clave del movimiento romántico español del siglo XIX. Integrando tanto figuras literarias como artistas visuales, se comprometieron con formas expresivas y emocionales, asà como con ideales liberales. Muchos mantuvieron conexiones con los cÃrculos románticos parisinos y contribuyeron a la modernización cultural de España a través del arte, el teatro, la literatura y la crÃtica social.", 1920 bibliography: [ 1921 "Donald E. Schurlknight, <i>Spanish Romanticism in Context: Of Subversion, Contradiction and Politics: Espronceda, Larra, Rivas, Zorrilla</i>, 1998.", 1922 "Begoña Torres González, 'La Hermandad Romántica: Larra y las artes plásticas', in <i>Larra: FÃgaro de vuelta, 1809-2009</i>, 2009, pp.183-203." 1923 ], 1924 websites: [] 1925 }, 1926 1927 "BORDEAUX_PARIS_CIRCLE": { 1928 members: [ 1929 "Leandro Fernández de MoratÃn", 1930 "JoaquÃn MarÃa Ferrer", 1931 "Juan Antonio Melón", 1932 "Antonio de Brugada", 1933 "Pierre Lacour", 1934 "Pierre Lacour fils", 1935 "Rosario Weiss Zorrilla", 1936 "Leocadia Zorrilla y Galarza", 1937 "Cyprien Gaulon", 1938 "Jacques Galos", 1939 "Carle Vernet" 1940 ], 1941 radius: 160, 1942 padding: 110, 1943 centerYOffset: 260, 1944 startAngle: -Math.PI / 2, 1945 sharedBoundaryNodes: { 1946 "Leandro Fernández de MoratÃn": Math.PI / 2, 1947 "Rosario Weiss Zorrilla": Math.PI / 2 1948 }, 1949 title: "Bordeaux-Paris Goya circle", 1950 titleEs: "CÃrculo Goya Burdeos-ParÃs", 1951 origin: "A transnational network of Spanish exiles, artists, and intellectuals centered on Francisco de Goya's final years in Bordeaux (1824-1828) and their connections to Paris. United by political liberalism, artistic commitment, and shared displacement from Spain, this group encompassed writers, painters, teachers, and family members connected through kinship, intellectual affinity, and the circumstance of exile.", 1952 originEs: "Un entramado transnacional de exiliados españoles, artistas e intelectuales centrado en los últimos años de Francisco de Goya en Burdeos (1824-1828) y sus conexiones con ParÃs. Unidos por liberalismo polÃtico, compromiso artÃstico y desplazamiento compartido desde España, este grupo abarcaba escritores, pintores, maestros y miembros de la familia conectados a través del parentesco, la afinidad intelectual y la circunstancia del exilio.", 1953 websites: [] 1954 }, 1955 1956 "GOYA_HISTORIANS_CRITICS": { 1957 members: [ 1958 "Charles Yriarte", 1959 "Eugène Piot", 1960 "Paul Lefort", 1961 "ValentÃn Carderera", 1962 "Théophile Gautier", 1963 "Théophile Thoré", 1964 "Gustave Brunet", 1965 "Laurent Matheron" 1966 ], 1967 radius: 150, 1968 padding: 100, 1969 centerYOffset: -140, 1970 startAngle: -Math.PI / 2, 1971 sharedBoundaryNodes: { 1972 "Charles Yriarte": Math.PI / 2, 1973 "ValentÃn Carderera": Math.PI / 2 1974 }, 1975 title: "Goya historians & critics", 1976 titleEs: "Historiadores y crÃticos de Goya", 1977 origin: "A diverse group of nineteenth-century art historians, critics, writers, and collectors who played a decisive role in the critical interpretation, scholarship, and dissemination of Francisco de Goya's work. Active primarily between the 1830s and 1890s, they combined connoisseurship with historical documentation, contributing to the first systematic cataloguing and critical analyses of his oeuvre. Their writings, collections, and curatorial practices helped establish Goya as a central figure in European art history, bridging Spanish and French intellectual contexts.", 1978 originEs: "Un grupo diverso de historiadores del arte, crÃticos, escritores y coleccionistas del siglo XIX que desempeñaron un papel decisivo en la interpretación crÃtica, la erudición y la difusión de la obra de Francisco de Goya. Activos principalmente entre la década de 1830 y 1890, combinaron el conocimiento experto con la documentación histórica, contribuyendo a los primeros análisis sistemáticos y crÃticos de su obra. Sus escritos, colecciones y prácticas curatoriales ayudaron a establecer a Goya como figura central en la historia del arte europeo, vinculando contextos intelectuales españoles y franceses.", 1979 bibliography: [ 1980 "Nigel Glendinning, <i>Goya and his Critics</i>
1980, 1977 (new edition, ed. Vega, 2017).", 1981 "Paula Fayos Pérez, <i>Goya's Caprichos in Nineteenth-Century France. Politics of the Grotesque</i>, 2024." 1982 ], 1983 websites: [] 1984 } 1985 }; 1986 1987 // Map clusterId to color 1988 const clusterColorMap = {}; 1989 let clusterColorIdx = 0; 1990 Object.keys(RADIAL_CLUSTERS).forEach(cid => { 1991 clusterColorMap[cid] = CLUSTER_COLORS[clusterColorIdx % CLUSTER_COLORS.length]; 1992 clusterColorIdx++; 1993 }); 1994 1995 clusterColorMap["GOYA_FAMILY"] = "#64b5f6"; // azul 1996 clusterColorMap["SPANISH_COURT_PAINTERS"] = "#ffb74d"; // naranja 1997 1998 // Reverse map: nodeId -> [clusterId1, clusterId2, ...] 1999 const nodeClusterMap = {}; 2000 Object.entries(RADIAL_CLUSTERS).forEach(([cid, cfg]) => { 2001 if (cfg.center) { 2002 if (!nodeClusterMap[cfg.center]) nodeClusterMap[cfg.center] = []; 2003 nodeClusterMap[cfg.center].push(cid); 2004 } 2005 (cfg.members || []).forEach(id => { 2006 if (!nodeClusterMap[id]) nodeClusterMap[id] = []; 2007 nodeClusterMap[id].push(cid); 2008 }); 2009 }); 2010 2011 let clusterEdgeColoringEnabled = true; 2012 2013 const getClusterIdForNode = nodeId => nodeClusterMap[nodeId] || []; 2014 const getClusterEdgeStatus = (fromId, toId) => { 2015 const fromClusters = getClusterIdForNode(fromId); 2016 const toClusters = getClusterIdForNode(toId); 2017 if (!fromClusters.length || !toClusters.length) return 'unclassified'; 2018 return fromClusters.some(c => toClusters.includes(c)) ? 'intra-cluster' : 'inter-cluster'; 2019 }; 2020 2021 const hexToRgba = (hex, alpha) => { 2022 if (!hex) return `rgba(100,100,100,${alpha})`; 2023 const shorthand = /^#([a-f\d])([a-f\d])([a-f\d])$/i; 2024 const fullHex = hex.replace(shorthand, (m, r, g, b) => `${r}${r}${g}${g}${b}${b}`); 2025 const match = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(fullHex); 2026 if (!match) return `rgba(100,100,100,${alpha})`; 2027 return `rgba(${parseInt(match[1], 16)}, ${parseInt(match[2], 16)}, ${parseInt(match[3], 16)}, ${alpha})`; 2028 }; 2029 2030 const getEdgeStyle = (edge) => { 2031 const level = edge.connection_level || 'direct'; 2032 const edgeStatus = getClusterEdgeStatus(edge.from, edge.to); 2033 2034 // Calculate width based on strength (1-5 scale) 2035 // If no strength specified, use default based on level 2036 let strengthWidth = 1; 2037 if (edge.strength) { 2038 // Map strength 1-5 to width 1.5-4.5 (more dramatic variation) 2039 strengthWidth = 1.5 + (edge.strength - 1) * 0.75; 2040 } 2041 2042 if (clusterEdgeColoringEnabled && edgeStatus === 'intra-cluster') { 2043 const fromClusters = getClusterIdForNode(edge.from); 2044 const toClusters = getClusterIdForNode(edge.to); 2045 const commonCluster = fromClusters.find(c => toClusters.includes(c)); 2046 const clusterColor = clusterColorMap[commonCluster] || '#646464'; 2047 const baseWidth = level === 'secondary' ? 1.4 : 1.8; 2048 return { 2049 color: hexToRgba(clusterColor, level === 'secondary' ? 0.45 : 0.8), 2050 width: edge.strength ? strengthWidth : baseWidth 2051 }; 2052 } 2053 const baseWidth = level === 'secondary' ? 1.2 : 1.5; 2054 return { 2055 color: level === 'secondary' ? 'rgba(150,150,150,0.15)' : 'rgba(200,200,200,0.25)', 2056 width: edge.strength ? strengthWidth : baseWidth 2057 }; 2058 }; 2059 2060 window.toggleClusterEdgeColoring = function () { 2061 clusterEdgeColoringEnabled = !clusterEdgeColoringEnabled; 2062 const button = document.getElementById('clusterToggleOption'); 2063 if (button) { 2064 button.textContent = clusterEdgeColoringEnabled ? t('BTN_CLUSTER_TOGGLE_ON') : t('BTN_CLUSTER_TOGGLE_OFF'); 2065 } 2066 edges.update(edges.get().map(edge => { 2067 const style = getEdgeStyle(edge); 2068 return {
2069 id: edge.id, 2070 color: { color: style.color }, 2071 width: style.width 2072 }; 2073 })); 2074 }; 2075 2076 let selectedClusterId = null; 2077 2078 const getClusterDisplayName = clusterId => { 2079 const cfg = RADIAL_CLUSTERS[clusterId]; 2080 if (!cfg) return clusterId || ''; 2081 return (CURRENT_LANG === 'es' ? cfg.titleEs : cfg.title) || clusterId; 2082 }; 2083 2084 const updateClusterInfoBadge = () => { 2085 const badge = document.getElementById('clusterInfoBadge'); 2086 if (!badge) return; 2087 badge.textContent = ''; 2088 }; 2089 2090 window.clearClusterSelection = function () { 2091 selectedClusterId = null; 2092 const option = document.getElementById('clusterSelect'); 2093 if (option) option.value = ''; 2094 nodes.update(nodes.get().map(node => ({ 2095 id: node.id, 2096 opacity: 1, 2097 borderWidth: 2 2098 }))); 2099 edges.update(edges.get().map(edge => { 2100 const style = getEdgeStyle(edge); 2101 return { 2102 id: edge.id, 2103 color: { color: style.color }, 2104 width: style.width 2105 }; 2106 })); 2107 updateClusterInfoBadge(); 2108 if (typeof window.showDefaultNodeInfo === 'function') { 2109 window.showDefaultNodeInfo(); 2110 } 2111 }; 2112 2113 window.selectCluster = function (clusterId) { 2114 if (!clusterId) { 2115 window.clearClusterSelection(); 2116 return; 2117 } 2118 2119 const cfg = RADIAL_CLUSTERS[clusterId]; 2120 if (!cfg) { 2121 window.clearClusterSelection(); 2122 return; 2123 } 2124 2125 selectedClusterId = clusterId; 2126 const members = new Set(Object.keys(nodeClusterMap).filter(id => nodeClusterMap[id].includes(clusterId))); 2127 const clusterColor = clusterColorMap[clusterId] || '#ffffff'; 2128 const highlightColor = hexToRgba(clusterColor, 0.85); 2129 2130 // Actually select the nodes in vis.js so they can be dragged together 2131 network.selectNodes(Array.from(members)); 2132 2133 nodes.update(nodes.get().map(node => ({ 2134 id: node.id, 2135 opacity: members.has(node.id) ? 1 : 0.25, 2136 borderWidth: members.has(node.id) ? 4 : 1 2137 }))); 2138 2139 edges.update(edges.get().map(edge => { 2140 const edgeStatus = getClusterEdgeStatus(edge.from, edge.to); 2141 if (edgeStatus === 'intra-cluster' && nodeClusterMap[edge.from].includes(clusterId) && nodeClusterMap[edge.to].includes(clusterId)) { 2142 return { 2143 id: edge.id, 2144 color: { color: highlightColor }, 2145 width: 2.4 2146 }; 2147 } 2148 return { 2149 id: edge.id, 2150 color: { color: 'rgba(200,200,200,0.08)' }, 2151 width: 1 2152 }; 2153 })); 2154 updateClusterInfoBadge(); 2155 2156 // Focus on cluster without glitching 2157 const memberArray = Array.from(members); 2158 if (memberArray.length > 0 && window.VIS_NETWORK) { 2159 try { 2160 const pos = window.VIS_NETWORK.getPositions(memberArray); 2161 // Calculate cluster center and bounds 2162 let cx = 0, cy = 0, minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity; 2163 memberArray.forEach(id => { 2164 cx += pos[id].x; 2165 cy += pos[id].y; 2166 minX = Math.min(minX, pos[id].x); 2167 maxX = Math.max(maxX, pos[id].x); 2168 minY = Math.min(minY, pos[id].y); 2169 maxY = Math.max(maxY, pos[id].y); 2170 }); 2171 cx /= memberArray.length; 2172 cy /= memberArray.length; 2173 2174 // Calculate appropriate scale based on cluster size 2175 const width = maxX - minX || 100; 2176 const height = maxY - minY || 100; 2177 const maxDim = Math.max(width, height); 2178 const scale = Math.min(1.5, 800 / (maxDim + 200)); // Fit with padding, max 1.5x zoom 2179 2180 // Move immediately without animation to avoid glitching 2181 window.VIS_NETWORK.moveTo({ 2182 position: { x: cx, y: cy }, 2183 scale: scale 2184 }); 2185 } catch (e) { 2186 console.warn('Error focusing on cluster:', e); 2187 } 2188 } 2189 2190 // Render cluster info in the right panel 2191 const nodeInfoEl = document.getElementById('nodeInfo'); 2192 if (nodeInfoEl) { 2193 const clusterTitle = getClusterDisplayName(clusterId); 2194 const sortedMembers = Array.from(members).sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' })); 2195 const isEs = CURRENT_LANG === 'es'; 2196 2197 let html = `<div class="cluster-info-panel">`; 2198 html += `<h3 style="margin:0 0 1rem 0; font-size:1.1rem; color:#fff; border-bottom:1px solid #444; padding-bottom:0.5rem;">${clusterTitle}</h3>`; 2199 2200 // Members section 2201 html += `<div class="section-heading" style="margin-bottom:0.4rem;">${isEs ? 'Miembros' : 'Members'}</div>`; 2202 html += `<ul style="margin:0 0 1rem 0; padding-left:1.2rem; line-height:1.8;">`;
2203 sortedMembers.forEach(id => { 2204 const safeId = id.replace(/'/g, "\\'"); 2205 html += `<li><a href="#" style="color:#66ccff;" onclick="focusNode('${safeId}'); return false;">${id}</a></li>`; 2206 }); 2207 html += `</ul>`; 2208 2209 // Origin section 2210 const originText = isEs ? (cfg.originEs || cfg.origin) : cfg.origin; 2211 if (originText) { 2212 const linkedOriginText = autoLinkNames(originText, nodesMap); 2213 html += `<div class="section-heading" style="margin-bottom:0.4rem;">${isEs ? 'Contexto' : 'Context'}</div>`; 2214 html += `<p style="margin:0 0 1rem 0; color:#ccc; line-height:1.5;">${linkedOriginText}</p>`; 2215 } 2216 2217 // Bibliography section 2218 if (cfg.bibliography && cfg.bibliography.length > 0) { 2219 html += `<div class="section-heading" style="margin-bottom:0.4rem;">${isEs ? 'BibliografÃa' : 'Bibliography'}</div>`; 2220 html += `<ul style="margin:0 0 1rem 0; padding-left:1.2rem; line-height:1.8; color:#ccc;">`; 2221 cfg.bibliography.forEach(entry => { 2222 html += `<li>${entry}</li>`; 2223 }); 2224 html += `</ul>`; 2225 } 2226 2227 // Websites section 2228 if (cfg.websites && cfg.websites.length > 0) { 2229 html += `<div class="section-heading" style="margin-bottom:0.4rem;">${isEs ? 'Sitios web' : 'Websites'}</div>`; 2230 html += `<ul style="margin:0 0 1rem 0; padding-left:1.2rem; line-height:1.8;">`; 2231 cfg.websites.forEach(site => { 2232 let label, url, description = ''; 2233 if (typeof site === 'string') { 2234 // Parse markdown link format: [label](url) optionally followed by description 2235 const match = site.match(/\[(.*?)\]\((.*?)\)(.*)/); 2236 if (match) { 2237 label = match[1]; 2238 url = match[2]; 2239 description = match[3].trim(); 2240 // Remove leading colon if present 2241 if (description.startsWith(':')) { 2242 description = description.substring(1).trim(); 2243 } 2244 } else { 2245 label = site; 2246 url = site; 2247 } 2248 } else { 2249 // Object format: {label, url, description} 2250 label = site.label; 2251 url = site.url; 2252 description = site.description || ''; 2253 } 2254 html += `<li><a href="${url}" target="_blank" rel="noopener noreferrer" style="color:#66ccff;">${label}</a>`; 2255 if (description) { 2256 html += `<br><span style="color:#999; font-size:0.9rem; margin-left:1rem; display:block; margin-top:0.2rem;">${description}</span>`; 2257 } 2258 html += `</li>`; 2259 }); 2260 html += `</ul>`; 2261 } 2262 2263 // Conferences section 2264 if (cfg.conferences && cfg.conferences.length > 0) { 2265 html += `<div class="section-heading" style="margin-bottom:0.4rem;">${isEs ? 'Conferencias' : 'Conferences'}</div>`; 2266 html += `<ul style="margin:0 0 1rem 0; padding-left:1.2rem; line-height:1.8; color:#ccc;">`; 2267 cfg.conferences.forEach(entry => { 2268 // Parse markdown links [label](url) - only show label without brackets, hide URL 2269 let linkedEntry = entry 2270 .replace(/\[(.*?)\]\((https?:\/\/[^\s)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer" style="color:#66ccff;">$1</a>'); 2271 html += `<li>${linkedEntry}</li>`; 2272 }); 2273 html += `</ul>`; 2274 } 2275 2276 html += `</div>`; 2277 nodeInfoEl.innerHTML = html; 2278 } 2279 }; 2280 2281 const clusterSelect = document.getElementById('clusterSelect'); 2282 if (clusterSelect) { 2283 const sortedClusterEntries = Object.entries(RADIAL_CLUSTERS) 2284 .sort((a, b) => { 2285 const nameA = getClusterDisplayName(a[0]); 2286 const nameB = getClusterDisplayName(b[0]); 2287 return nameA.localeCompare(nameB, undefined, { sensitivity: 'base' }); 2288 }); 2289 2290 clusterSelect.innerHTML = `<option value="">${t('SELECT_CLUSTER_OPTION')}</option>` + 2291 sortedClusterEntries.map(([cid]) => { 2292 const name = getClusterDisplayName(cid); 2293 return `<option value="${cid}">${name}</option>`; 2294 }).join(''); 2295 2296 clusterSelect.addEventListener('change', (e) => { 2297 window.selectCluster(e.target.value); 2298 }); 2299 } 2300
2301 const initClusterControls = () => { 2302 const button = document.getElementById('clusterToggleButton'); 2303 if (button) { 2304 button.textContent = clusterEdgeColoringEnabled ? t('BTN_CLUSTER_TOGGLE_ON') : t('BTN_CLUSTER_TOGGLE_OFF'); 2305 } 2306 updateClusterInfoBadge(); 2307 }; 2308 2309 initClusterControls(); 2310 2311 // Ajustes de nodos con cluster color 2312 // â NUEVO CÃDIGO (aplica posiciones) 2313 nodes = new vis.DataSet(data.nodes.map(node => { 2314 const degree = edgeCount[node.id] || 1; 2315 const clusterIds = nodeClusterMap[node.id]; 2316 const clusterColor = clusterIds && clusterIds.length > 0 ? clusterColorMap[clusterIds[0]] : undefined; 2317 2318 // ð¥ APLICAR POSICIONES DESDE positions_config.json 2319 const pos = nodePositions[node.id] || { x: undefined, y: undefined }; 2320 2321 const config = { 2322 ...node, 2323 x: pos.x, // <-- AÃADIR ESTO 2324 y: pos.y, // <-- AÃADIR ESTO 2325 size: Math.min(13 + degree * 0.72, 38), 2326 mass: 1 + degree * 0.025, 2327 font: { 2328 size: Math.min(11 + degree * 0.42, 18), 2329 color: '#ffffff', 2330 strokeWidth: 3, 2331 strokeColor: '#111111', 2332 face: 'EB Garamond, serif', 2333 align: 'center', 2334 bold: true, 2335 vadjust: -10 2336 }, 2337 color: clusterColor ? { background: clusterColor, border: '#2B7CE9' } : { border: '#2B7CE9' }, 2338 borderWidth: 2, 2339 shape: node.image ? 'circularImage' : 'dot', 2340 labelHighlightBold: false, 2341 margin: -10 2342 }; 2343 config._imageUrl = node.image; 2344 nodesMap[node.id] = config; 2345 return config; 2346 })); 2347 2348 // Edges más transparentes (general) 2349 edges = new vis.DataSet(data.edges.map(edge => { 2350 const level = edge.connection_level || "direct"; 2351 const edgeStatus = getClusterEdgeStatus(edge.from, edge.to); 2352 2353 // Muestra etiqueta solo si es "direct" o "secondary". 2354 // Si termina en "?" (p. ej. "direct?") NO la pintamos y la ponemos como tooltip. 2355 const label = 2356 edge.label === 'direct' || edge.label === 'secondary' 2357 ? edge.label 2358 : undefined; 2359 2360 const title = 2361 edge.label && /\?$/.test(edge.label) 2362 ? edge.label // aparecerá al pasar el ratón 2363 : edge.title; 2364 2365 const style = getEdgeStyle(edge); 2366 2367 // Calculate spring length based on strength (1-5 scale) 2368 // Respect strength for BOTH intra and inter-cluster edges 2369 let edgeLength = 210; // default 2370 if (edge.strength) { 2371 const strength = Math.max(1, Math.min(5, edge.strength)); // clamp 1-5 2372 2373 if (edgeStatus === 'inter-cluster') { 2374 // Inter-cluster edges: use longer base length but STILL apply strength 2375 // Level-1 strength = tighter pull even across clusters (important for key relationships) 2376 edgeLength = 120 + (strength - 1) * 50; // strength 1-5 = 120-320px range (base 200 for inter-cluster) 2377 } else { 2378 // Intra-cluster edges: apply strength normally to pull related nodes together 2379 // Make strength-1 edges extremely tight (20px) for very close relationships 2380 edgeLength = 20 + (strength - 1) * 70; // strength 1-5 = 20-300px range 2381 } 2382 } 2383 2384 const processedEdge = { 2385 ...edge, 2386 label, 2387 title, 2388 color: { color: style.color }, 2389 width: style.width, 2390 physics: true, // ensure physics is enabled 2391 length: edgeLength // vis.js uses 'length' property for edge spring length 2392 }; 2393 2394 return processedEdge; 2395 })); 2396 window.edges = edges; 2397 2398 // ===================== MINI-FAMILIAS (anchors invisibles) ===================== 2399 // NOTA: esto NO toca tus edges reales. Añade nodos/edges "fantasma" para compactar grupos. 2400 2401 window.__clusterOf = {}; // nodeId -> clusterId 2402 2403 Object.entries(RADIAL_CLUSTERS).forEach(([clusterId, cfg]) => { 2404 if (cfg.center && nodes.get(cfg.center)) { 2405 if (!window.__clusterOf[cfg.center]) window.__clusterOf[cfg.center] = []; 2406 window.__clusterOf[cfg.center].push(clusterId); 2407 } 2408
2409 (cfg.members || []).forEach(id => { 2410 if (!nodes.get(id)) return; 2411 if (!window.__clusterOf[id]) window.__clusterOf[id] = []; 2412 window.__clusterOf[id].push(clusterId); 2413 }); 2414 }); 2415 2416 /* 2417 (function addClusterAnchors(nodes, edges, clusters) { 2418 const anchorNodes = []; 2419 const anchorEdges = []; 2420 2421 Object.entries(clusters).forEach(([clusterId, cfg]) => { 2422 const members = (cfg?.members || []).filter(id => nodes.get(id)); 2423 const center = cfg?.center && nodes.get(cfg.center) ? cfg.center : null; 2424 2425 if (center) { 2426 window.__clusterOf[center] = clusterId; 2427 } 2428 members.forEach(id => { window.__clusterOf[id] = clusterId; }); 2429 if (members.length < 2) return; 2430 2431 const anchorId = `ANCHOR__${clusterId}`; 2432 2433 // Nodo-ancla invisible (no aparece en pantalla) 2434 anchorNodes.push({ 2435 id: anchorId, 2436 label: "", 2437 shape: "dot", 2438 size: 1, 2439 mass: 0.01, 2440 clusterAnchor: true, 2441 physics: true, 2442 color: { border: "rgba(0,0,0,0)", background: "rgba(0,0,0,0)" }, 2443 hidden: true, 2444 selectable: false 2445 }); 2446 2447 const L = typeof cfg.radius === "number" ? cfg.radius : 60; 2448 2449 // Aristas invisibles cortas hacia cada miembro 2450 members.forEach(memberId => { 2451 const pulls = cfg?.pulls ?? 3; // nº de âmuellesâ paralelos (más = más compacto) 2452 for (let k = 0; k < pulls; k++) { 2453 anchorEdges.push({ 2454 id: `AEDGE__${clusterId}__${memberId}__${k}`, 2455 from: anchorId, 2456 to: memberId, 2457 physics: true, 2458 smooth: false, 2459 _isClusterEdge: true, 2460 length: L, 2461 width: 0.1, 2462 selectionWidth: 0, 2463 hoverWidth: 0, 2464 color: { color: "rgba(0,0,0,0)" } 2465 }); 2466 } 2467 }); 2468 }); 2469 2470 if (anchorNodes.length) nodes.add(anchorNodes); 2471 if (anchorEdges.length) edges.add(anchorEdges); 2472 })(nodes, edges, CLUSTERS); 2473 */ 2474 // =================== /MINI-FAMILIAS (anchors invisibles) =================== 2475 2476 2477 const locale = (CURRENT_LANG === 'es') ? 'es-ES' : 'en-GB'; 2478 2479 const lastUpdate = data.last_update; 2480 2481 const formattedUpdate = lastUpdate 2482 ? new Date(lastUpdate + 'T00:00:00').toLocaleDateString( 2483 locale, 2484 { day: 'numeric', month: 'long', year: 'numeric' } 2485 ) 2486 : t('Unknown'); 2487 2488 document.getElementById("networkStats").innerHTML = 2489 `${t('Nodes')}: ${data.nodes.length} | ${t('Connections')}: ${data.edges.length}<br> 2490 <span style="font-size: 0.8rem; color: #999;">${window.LAST_UPDATE_LABEL}: ${formattedUpdate}</span>`; 2491 2492 2493 // ===== NEW NODES 2494 function buildNewNodesList(data) { 2495 const container = document.getElementById('newNodesList'); 2496 if (!container) return; 2497 2498 const CUTOFF_STR = '2025-04-01'; 2499 2500 const items = (data.nodes || []) 2501 .filter(n => typeof n.added === 'string' && n.added >= CUTOFF_STR) 2502 .map(n => ({ 2503 id: n.id, 2504 label: (CURRENT_LANG === 'es' && n['display name es']) ? n['display name es'] : (n.label || n.id), 2505 date: n.added 2506 })) 2507 .sort((a, b) => new Date(b.date) - new Date(a.date)); 2508 2509 if (!items.length) { 2510 container.innerHTML = `<em>${CURRENT_LANG === 'es' 2511 ? 'No hay nodos añadidos desde abril de 2025.' 2512 : 'No nodes added since April 2025.'}</em>`; 2513 return; 2514 } 2515 2516 container.innerHTML = ''; 2517 2518 items.forEach(item => { 2519 const wrapper = document.createElement('div'); 2520 wrapper.className = 'new-node-item'; 2521 2522 const a = document.createElement('a'); 2523 a.href = '#'; 2524 a.style.color = '#66ccff'; 2525 a.textContent = item.label; 2526 2527 const dateSpan = document.createElement('span'); 2528 dateSpan.className = 'new-node-date'; 2529 2530 const locale = (CURRENT_LANG === 'es') ? 'es-ES' : 'en-GB'; 2531 const dateStr = new Date(item.date + 'T00:00:00') 2532 .toLocaleDateString(locale, { day: '2-digit', month: 'short', year: 'numeric' }); 2533 2534 dateSpan.textContent = ` â ${dateStr}`; 2535 2536 a.onclick = (e) => { 2537 e.preventDefault(); 2538 if (typeof focusNode === 'function') focusNode(item.id); 2539 }; 2540 2541 wrapper.appendChild(a); 2542 wrapper.appendChild(dateSpan); 2543 container.appendChild(wrapper); 2544 }); 2545 } 2546 2547 buildNewNodesList(data); 2548 2549 const newNodesToggle = document.getElementById('newNodesToggle'); 2550 const newNodesDropdown = document.getElementById('newNodesDropdown'); 2551 2552 if (newNodesToggle && newNodesDropdown) { 2553 newNodesToggle.addEventListener('click', function (e) { 2554 e.stopPropagation(); 2555 const isOpen = newNodesDropdown.style.display === 'block'; 2556 newNodesDropdown.style.display = isOpen ? 'none' : 'block'; 2557 newNodesToggle.classList.toggle('open', !isOpen); 2558 }); 2559 2560 document.addEventListener('click', function (e) { 2561 if (!document.getElementById('newNodesSection')?.contains(e.target)) { 2562 newNodesDropdown.style.display = 'none'; 2563 newNodesToggle.classList.remove('open'); 2564 } 2565 }); 2566 } 2567 2568 // ---- MEMBERS LIST: manual overrides (edit these if needed) ---- 2569 // 1) Para nombres completos donde quieras forzar el apellido clave (usa la grafÃa exacta) 2570 2571 // Declarar una sola vez (arriba, antes de surnameKey) 2572 const SURNAME_FORCE_BY_FULLNAME = Object.create(null); 2573 const GIVEN_FORCE_TOKENS = new Set(); 2574 const SURNAME_FORCE_TOKENS = new Set(); 2575 2576 // Rellenar el diccionario/sets (puedes repetir este bloque en otros sitios sin problema) 2577 Object.assign(SURNAME_FORCE_BY_FULLNAME, { 2578 'Weiss Zorrilla': 'weiss', 2579 'Prince Antoine, Duke of Montpensier': 'montpensier', 2580 'Juan AgustÃn Ceán Bermúdez': 'cean', 2581 'II Duke of San Carlos': 'san carlos', 2582 'X Marchioness of Santa Cruz': 'santa cruz', 2583 'MarÃa Antonia Gonzaga, Marchioness of Villafranca (widow)': 'villafranca', 2584 'Berthe Morisot': 'morisot', 2585 'Antoine-Jean Gros': 'gros', 2586 'Francisco Ãlvarez de Toledo, XII Marquis of Villafranca': 'villafranca', 2587 'José Ãlvarez de Toledo, Duke of Alba': 'alba', 2588 'MarÃa Teresa de Silva, XIII Duchess of Alba': 'alba', 2589 'MarÃa Cristina de Borbón-Dos Sicilias': 'borbon', 2590 'Rosario Weiss Zorrilla': 'weiss', 2591 'Isabel II': 'isabel', 2592 'Carlos III': 'carlos', 2593 'Carlos IV': 'carlos', 2594 'Fernando VII': 'fernando', 2595 'Louis Philippe I': 'louis', 2596 'MarÃa Tomasa Palafox, Marchioness of Villafranca': 'villafranca', 2597 'MarÃa Gabriela de Palafox, Marchioness of Lazán': 'lazan', 2598 'Eugenio Eulalio Palafox, VII Count of Montijo': 'montijo', 2599 'Cipriano Portocarrero, VIII Count of Montijo': 'montijo', 2600 'MarÃa Francisca de Sales Portocarrero, VI Countess of Montijo': 'montijo', 2601 'Alexandre Dumas père': 'dumas', 2602 'Francisco MartÃnez de la Rosa': 'martinez',
2603 'Juan Meléndez Valdés': 'melendez', 2604 'Jenaro Pérez Villaamil': 'perez', 2605 'Antonio Remón Zarco del Valle y Huet': 'remon', 2606 'MarÃa de las Mercedes Santa Cruz y Montalvo, Countess of Merlin': 'merlin', 2607 'Ramón de Mesonero Romanos': 'mesonero', 2608 'Gregorio Cruzada Villaamil': 'cruzada', 2609 'Juan Fernández de Rojas': 'fernandez', 2610 'Manuel GarcÃa de la Prada': 'garcia', 2611 'Dominique Vivant Denon': 'vivant', 2612 'Pierre Lacour fils': 'lacour', 2613 'Madame Ewelina Hanska': 'hanska' 2614 }); 2615 2616 window.__GN_SURNAME_FORCE_BY_FULLNAME = SURNAME_FORCE_BY_FULLNAME; 2617 2618 /* ---- MEMBERS LIST: alphabetical index of nodes (AâZ by surname) ---- */ 2619 function surnameKey(name) { 2620 if (!name) return ''; 2621 2622 // Normaliza a "base": quita paréntesis y corta por coma (para eliminar tÃtulos) 2623 let base = String(name) 2624 .replace(/\(.*?\)/g, '') // (widow), (husband of), etc. 2625 .split(',')[0] // ", Duke of Alba" fuera 2626 .replace(/[.]/g, ' ') 2627 .replace(/\s+/g, ' ') 2628 .trim(); 2629 2630 // Helper: quitar acentos y pasar a minúsculas 2631 const fold = s => s.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase(); 2632 2633 // 0) Override por nombre completo (y por base limpia) 2634 const exactName = String(name).trim(); 2635 const override = SURNAME_FORCE_BY_FULLNAME[name] || SURNAME_FORCE_BY_FULLNAME[exactName] || SURNAME_FORCE_BY_FULLNAME[base]; 2636 if (override) { 2637 return fold(override); 2638 } 2639
2640 const tokens = base.split(' ').filter(Boolean); 2641 if (!tokens.length) return ''; 2642 2643 const tf = tokens.map(fold); 2644 2645 const particles = new Set([ 2646 'de',"d'",'del','de-la','de-las','de-los','la','las','los','le','les', 2647 'du','des','d','da','di','do','dos','van','von','der','den','zu','zur', 2648 'y','e','the','of' 2649 ]); 2650 2651 const suffixes = new Set(['jr','junior','fils','hijo']); 2652 const titles = new Set([ 2653 'duchess','duke','count','countess','marquis','marchioness', 2654 'queen','king','prince','princess','emperor','empress' 2655 ]); 2656 const roman = /^[IVXLCDM]+$/i; 2657 2658 // 1) Si hay un tÃtulo con "of â¦" (Duchess of Abrantes), coge el último token 2659 if (tf.some(t => titles.has(t)) && tf.includes('of') && tokens.length >= 2) { 2660 return fold(tokens[tokens.length - 1]); 2661 } 2662 2663 // 2) Caso "X y Y": toma el anterior a 'y' (apellido español tradicional) 2664 const yIndex = tf.indexOf('y'); 2665 if (yIndex > 0 && yIndex < tf.length - 1) { 2666 return fold(tokens[yIndex - 1]); 2667 } 2668 2669 // Helper para validar tokens como posibles apellidos 2670 const normalizeToken = (raw) => { 2671 const t = fold(raw).replace(/['â]/g,''); 2672 if (!t) return ''; 2673 if (particles.has(t)) return ''; 2674 if (suffixes.has(t)) return ''; 2675 if (roman.test(t)) return ''; 2676 return t; 2677 }; 2678 2679 // 3) Fuerza de apellido por token (opcional) 2680 if (SURNAME_FORCE_TOKENS.has(tf[tf.length - 1])) { 2681 return tf[tf.length - 1]; 2682 } 2683 2684 // 4) Dos tokens: por defecto usa el ÃLTIMO (Nombre Apellido), 2685 // salvo que el primero esté marcado como nombre (forzado) o compuesto de nombres. 2686 if (tokens.length === 2) { 2687 const first = tf[0]; 2688 const second = normalizeToken(tokens[1]) || tf[1]; 2689 2690 // "Antoine-Jean" u otros compuestos: si todas las partes están forzadas como nombre â usa el segundo 2691 const isHyphenGiven = first.includes('-') && 2692 first.split('-').every(p => GIVEN_FORCE_TOKENS.has(p)); 2693 2694 const firstForcedGiven = GIVEN_FORCE_TOKENS.has(first) || isHyphenGiven; 2695 2696 // Si el segundo está forzado como apellido, úsalo 2697 if (SURNAME_FORCE_TOKENS.has(second)) return second; 2698 2699 // En casi todos los casos de 2 tokens queremos el ÃLTIMO (Apellido). 2700 // EXCEPCIÃN: si quieres que un caso 2-apellidos españoles cuente por el primero, 2701 // añade el nombre completo arriba en SURNAME_FORCE_BY_FULLNAME. 2702 if (firstForcedGiven) { 2703 return second; // Nombre compuesto forzado â usa Apellido 2704 } 2705 return second; // default: último token como apellido 2706 } 2707 2708 // 5) General: recorre desde el final buscando un token válido 2709 for (let i = tokens.length - 1; i >= 0; i--) { 2710 const n = normalizeToken(tokens[i]); 2711 if (n) return n; 2712 } 2713 2714 // 6) Fallback: último token "tal cual" 2715 return tf[tf.length - 1]; 2716 } 2717 2718 function buildMembersList(data = window.__GN_DATA) { 2719 const container = document.getElementById('membersList'); 2720 if (!container) return; 2721 2722 const items = (data?.nodes || []) 2723 .map(n => ({ 2724 id: n.id, 2725 display: (CURRENT_LANG === 'es' && n['display name es']) ? n['display name es'] : n.id 2726 })) 2727 .filter(x => x.id); 2728 2729 const collator = new Intl.Collator(undefined, { sensitivity: 'base' }); 2730 items.sort((a,b) => collator.compare(surnameKey(a.id), surnameKey(b.id))); 2731 2732 const esc = s => String(s).replace(/\\/g,'\\\\').replace(/'/g,"\\'"); 2733 2734 let html = ''; 2735 let currentLetter = ''; 2736 items.forEach(({id, display}) => { 2737 const key = surnameKey(id); 2738 const letter = (key.charAt(0) || '#').toUpperCase(); 2739 if (letter !== currentLetter) { 2740 if (currentLetter) html += '</ul>'; 2741 html += `<div class="section-heading" style="margin-top:0.8rem;">${letter}</div><ul style="list-style:none; padding-left:0.5rem; margin:0;">`; 2742 currentLetter = letter; 2743 } 2744 html += `<li style="margin:0.1rem 0;"><a href="#" onclick="focusNode('${esc(id)}'); return false;" style="color:#66ccff; text-decoration:none;">${display}</a></li>`; 2745 }); 2746 if (items.length) html += '</ul>'; 2747 2748 container.innerHTML = html; 2749 } 2750 2751 window.buildMembersList = buildMembersList; 2752 buildMembersList(data); 2753 2754 let lastHighlightedNode = null; 2755 let lastHighlightedNodes = []; 2756 let lastNonHighlightedNodes = []; 2757 let activeEdgeIds = new Set(); // Edges activos (los conectados al nodo resaltado) 2758 2759 // âââ limpia SOLO el "rojo" de nodos; no toca edges ni estados globales âââ 2760 function clearNodeHighlightsOnly() { 2761 const nodeUpdates = []; 2762 2763 if (lastHighlightedNode) { 2764 nodeUpdates.push({ 2765 id: lastHighlightedNode, 2766 color: { border: '#2B7CE9' }, 2767 borderWidth: 2, 2768 opacity: 1 2769 }); 2770 } 2771 2772 if (lastHighlightedNodes && lastHighlightedNodes.length > 0) {
2773 lastHighlightedNodes.forEach(id => { 2774 nodeUpdates.push({ 2775 id, 2776 color: { border: '#2B7CE9' }, 2777 borderWidth: 2, 2778 opacity: 1 2779 }); 2780 }); 2781 } 2782 2783 if (nodeUpdates.length > 0) nodes.update(nodeUpdates); 2784 2785 // reset SOLO de nodos 2786 lastHighlightedNode = null; 2787 lastHighlightedNodes = []; 2788 } 2789 2790 // Exponer a window para que filterGraph() (global) pueda limpiar y registrar highlights 2791 window.__GN_clearHighlights = clearHighlights; 2792 window.__GN_setLastHighlightedNodes = function (ids) { 2793 lastHighlightedNodes = Array.isArray(ids) ? ids : []; 2794 }; 2795 2796 function clearHighlights() { 2797 // Batch update nodes 2798 const nodeUpdates = []; 2799 2800 if (lastHighlightedNode) { 2801 nodeUpdates.push({ 2802 id: lastHighlightedNode, 2803 color: { border: '#2B7CE9' }, 2804 borderWidth: 2, 2805 opacity: 1 // Reset opacity 2806 }); 2807 } 2808 2809 if (lastHighlightedNodes.length > 0) { 2810 lastHighlightedNodes.forEach(id => { 2811 nodeUpdates.push({ 2812 id, 2813 color: { border: '#2B7CE9' }, 2814 borderWidth: 2, 2815 opacity: 1 // Reset opacity 2816 }); 2817 }); 2818 } 2819 2820 // Reset opacity for non-highlighted nodes 2821 if (lastNonHighlightedNodes.length > 0) { 2822 lastNonHighlightedNodes.forEach(id => { 2823 nodeUpdates.push({ 2824 id, 2825 opacity: 1 // Reset to fully opaque 2826 }); 2827 }); 2828 } 2829 2830 if (nodeUpdates.length > 0) nodes.update(nodeUpdates); 2831 2832 // Batch update edges 2833 const edgeUpdates = edges.get().map(edge => { 2834 const style = getEdgeStyle(edge); 2835 return { 2836 id: edge.id, 2837 color: { color: style.color }, 2838 width: style.width 2839 }; 2840 }); 2841 edges.update(edgeUpdates); 2842 2843 // ð Reset de estados 2844 lastHighlightedNode = null; 2845 lastHighlightedNodes = []; 2846 lastNonHighlightedNodes = []; 2847 activeEdgeIds = new Set(); 2848 } 2849 2850 const container = document.getElementById('network'); 2851 if (!container) { 2852 console.error("â No se encontró el contenedor #network."); 2853 return; 2854 } 2855 2856 // Check if we have a specific node/edge hash URL - for logging purposes only 2857 console.log(`Hash URL: ${window.location.hash}`); 2858 2859 const network = new vis.Network(container, { nodes, edges }, { 2860 nodes: { 2861 borderWidth: 2, 2862 shapeProperties: { 2863 useBorderWithImage: true 2864 } 2865 }, 2866 edges: { 2867 color: 'rgba(200,200,200,0.2)', 2868 width: 1, 2869 selectionWidth: 5, // aumenta el área clicable al seleccionar 2870 hoverWidth: 3, // facilita el clic al pasar el ratón 2871 smooth: { type: 'continuous' } // â más rápido y visualmente igual en tu caso (antes: dynamic) 2872 }, 2873 // Process edge strength to adjust width 2874 // (actual processing happens in edges.map() after loading JSON) 2875 2876 physics: { 2877 enabled: true, 2878 solver: 'repulsion', 2879 repulsion: { 2880 nodeDistance: 600, // increased from 500 for stronger node separation and collision prevention 2881 centralGravity: 0.018, // â Slightly less gravity 2882 springLength: 210, // â Longer springs for more separation 2883 springConstant: 0.012, // â Softer springs 2884 damping: 0.9 // â Fast settling (increased from 0.52) 2885 }, 2886 stabilization: { 2887 iterations: 200, // Limit iterations for faster settling 2888 fit: true 2889 }, 2890 maxVelocity: 50 // Limit velocity to prevent wild oscillations 2891 }, 2892 2893 // ð¥ AÃADE ESTA NUEVA OPCIÃN (ANTI-OVERLAP INTEGRADO): 2894 layout: { 2895 improvedLayout: false, 2896 randomSeed: 1912, 2897 hierarchical: { 2898 enabled: false 2899 } 2900 }, 2901 2902 // ð¥ Y ESTA OPCIÃN CRUCIAL: 2903 interaction: { 2904 dragNodes: true, 2905 hideEdgesOnDrag: false, 2906 hideNodesOnDrag: false, 2907 zoomView: true, // â permite hacer zoom 2908 dragView: true, // â permite mover la vista (pan)
2909 navigationButtons: false // Disable built-in buttons, we'll create custom ones 2910 }, 2911 2912 // ð¥ Y ESTA MÃS: 2913 configure: { 2914 enabled: false, 2915 filter: 'physics' 2916 } 2917 }); 2918 // Add canvas click handler to deselect cluster when clicking background 2919 network.on('click', function(params) { 2920 if (params.nodes.length === 0 && params.edges.length === 0 && selectedClusterId) { 2921 window.clearClusterSelection(); 2922 } 2923 }); 2924 2925 window.VIS_NETWORK = network; 2926 2927 // Move navigation buttons inside network container so they appear within the network area 2928 const panButtons = document.getElementById('customPanButtons'); 2929 const zoomButtons = document.getElementById('customZoomButtons'); 2930 if (panButtons && panButtons.parentElement.id !== 'network') { 2931 container.appendChild(panButtons); 2932 } 2933 if (zoomButtons && zoomButtons.parentElement.id !== 'network') { 2934 container.appendChild(zoomButtons); 2935 } 2936 2937 // ===== ADMIN MODE ===== 2938 const urlParams = new URLSearchParams(window.location.search); 2939 const isAdminMode = urlParams.get('admin') === 'true'; 2940 console.log('Admin Mode:', isAdminMode ? 'ENABLED' : 'DISABLED'); 2941 2942 // Show/hide admin controls 2943 if (isAdminMode) { 2944 const adminPanel = document.createElement('div'); 2945 adminPanel.style.cssText = ` 2946 position: fixed; top: 10px; right: 10px; background: rgba(255, 0, 0, 0.9); 2947 color: white; padding: 10px 15px; border-radius: 5px; font-size: 12px; z-index: 9999; 2948 font-family: monospace; 2949 `; 2950 adminPanel.innerHTML = 'ð ADMIN MODE ACTIVE<br><button id="downloadLayoutBtn" style="margin-top: 5px; padding: 5px 10px; cursor: pointer;">Download Layout</button>'; 2951 document.body.appendChild(adminPanel); 2952 2953 document.getElementById('downloadLayoutBtn').addEventListener('click', function() { 2954 const positions = network.getPositions(nodes.getIds()); 2955 const dataStr = JSON.stringify(positions, null, 2); 2956 const dataBlob = new Blob([dataStr], { type: 'application/json' }); 2957 const url = URL.createObjectURL(dataBlob); 2958 const link = document.createElement('a'); 2959 link.href = url; 2960 link.download = 'positions_config.json'; 2961 link.click(); 2962 console.log('Downloaded positions_config.json'); 2963 }); 2964 } else { 2965 // Disable node dragging but allow view panning for regular users 2966 network.setOptions({ interaction: { dragNodes: false, dragView: true } }); 2967 } 2968 2969 // ===== CLUSTER POSITION PERSISTENCE ===== 2970 let dragTimeout; 2971 2972 // Only save positions when dragging ends IN ADMIN MODE 2973 if (isAdminMode) { 2974 network.on('dragEnd', function(params) { 2975 clearTimeout(dragTimeout); 2976 dragTimeout = setTimeout(() => { 2977 const allNodeIds = nodes.getIds(); 2978 const positions = network.getPositions(allNodeIds); 2979 allNodeIds.forEach(nodeId => { 2980 if (positions[nodeId]) { 2981 nodePositions[nodeId] = { x: positions[nodeId].x, y: positions[nodeId].y }; 2982 } 2983 }); 2984 localStorage.setItem('nodePositions', JSON.stringify(nodePositions)); 2985 console.log('Saved ALL', allNodeIds.length, 'node positions'); 2986 }, 500); 2987 }); 2988 } 2989 2990 // Apply saved node positions on load - DEFINED AS GLOBAL 2991 window.applySavedNodePositions = function() { 2992 if (Object.keys(nodePositions).length > 0) { 2993 const savedNodeIds = Object.keys(nodePositions); 2994 const positions = network.getPositions(savedNodeIds); 2995 savedNodeIds.forEach(nodeId => { 2996 if (nodePositions[nodeId]) { 2997 network.moveNode(nodeId, nodePositions[nodeId].x, nodePositions[nodeId].y); 2998 } 2999 }); 3000 console.log('Restored ALL positions for', savedNodeIds.length, 'nodes'); 3001 } 3002 }; 3003 3004 3005 // Add reset function 3006 window.resetNodePositions = function() { 3007 localStorage.removeItem('nodePositions'); 3008 nodePositions = {}; 3009 console.log('Reset all node positions'); 3010 location.reload(); 3011 }; 3012 3013 const HALO_PRIORITY_BY_NODE = { 3014 "Francisco de Goya": 30, 3015 "Adrien Dauzats": 28, 3016 "ValentÃn Carderera": 25, 3017 "Federico de Madrazo": 28, 3018 "Eugène Delacroix": 30, 3019 "Charles Baudelaire": 22, 3020 "Nadar": 22, 3021 "Manuel Godoy": 22, 3022 "Mariano José Larra": 18, 3023 "Mariano Salvador Maella": 18, 3024 "Eugenia de Montijo": 20, 3025 "Richard Ford": 18, 3026 "Edgar Degas": 20,
3027 "Théophile Gautier": 25, 3028 "Achille Devéria": 18, 3029 "Eugène Piot": 20, 3030 "Eugenio Ochoa": 20, 3031 "Carlos Luis de Ribera": 20 3032 }; 3033 3034 function getNodeHalo(node) { 3035 const size = node?.size || 25; 3036 const fontSize = node?.font?.size || 16; 3037 const id = node?.id || node?.label || ''; 3038 const priorityExtra = HALO_PRIORITY_BY_NODE[id] || 0; 3039 3040 // halo visual = tamaño del nodo + peso de etiqueta + colchón extra 3041 return size + (fontSize * 1.8) + 18 + priorityExtra; 3042 } 3043 3044 function enforceGlobalNodeHalo(network, nodes, passes = 20) { 3045 const ids = nodes.getIds().filter(id => !String(id).startsWith('ANCHOR__')); 3046 3047 for (let p = 0; p < passes; p++) { 3048 const pos = network.getPositions(ids); 3049 let movedAny = false; 3050 3051 for (let i = 0; i < ids.length; i++) { 3052 for (let j = i + 1; j < ids.length; j++) { 3053 const a = ids[i]; 3054 const b = ids[j]; 3055 3056 const pA = pos[a]; 3057 const pB = pos[b]; 3058 if (!pA || !pB) continue; 3059 3060 const nodeA = nodes.get(a); 3061 const nodeB = nodes.get(b); 3062 if (!nodeA || !nodeB) continue; 3063 3064 const haloA = getNodeHalo(nodeA); 3065 const haloB = getNodeHalo(nodeB); 3066 const minD = haloA + haloB; 3067 3068 let dx = pB.x - pA.x; 3069 let dy = pB.y - pA.y; 3070 let d = Math.hypot(dx, dy); 3071 3072 // evita división por cero 3073 if (d < 0.001) { 3074 dx = 1; 3075 dy = 0; 3076 d = 1; 3077 } 3078 3079 if (d < minD) { 3080 const overlap = minD - d; 3081 const ux = dx / d; 3082 const uy = dy / d; 3083 3084 // mover cada nodo la mitad 3085 const shift = overlap / 2; 3086 3087 network.moveNode(a, pA.x - ux * shift, pA.y - uy * shift); 3088 network.moveNode(b, pB.x + ux * shift, pB.y + uy * shift); 3089 3090 movedAny = true; 3091 } 3092 } 3093 } 3094 3095 // si ya no ha movido nada, salir antes 3096 if (!movedAny) break; 3097 } 3098 } 3099 3100 // ð¥ SOLUCIÃN NUCLEAR ANTI-OVERLAP (MULTI-PASS, RADIO REAL) 3101 function nudgeOverlaps(network, nodes, clusterOf = null, passes = 5) { 3102 for (let p = 0; p < passes; p++) { 3103 const ids = nodes.getIds().filter(id => !String(id).startsWith('ANCHOR__')); 3104 const pos = network.getPositions(ids); 3105 3106 for (let i = 0; i < ids.length; i++) { 3107 for (let j = i + 1; j < ids.length; j++) { 3108 const a = ids[i], b = ids[j]; 3109 // Permitir separación incluso dentro del mismo cluster 3110 const pA = pos[a], pB = pos[b]; 3111 if (!pA || !pB) continue; 3112 3113 const nodeA = nodes.get(a); 3114 const nodeB = nodes.get(b); 3115 3116 const baseA = (nodeA?.size || 25) + ((nodeA?.font?.size || 16) * 1.8); 3117 const baseB = (nodeB?.size || 25) + ((nodeB?.font?.size || 16) * 1.8); 3118 3119 const extraA = HALO_PRIORITY_BY_NODE[nodeA?.id || ''] || 0; 3120 const extraB = HALO_PRIORITY_BY_NODE[nodeB?.id || ''] || 0; 3121 3122 const minD = baseA + baseB + 24 + extraA + extraB; 3123 3124 const dx = pB.x - pA.x; 3125 const dy = pB.y - pA.y; 3126 const d = Math.hypot(dx, dy) || 1; 3127 3128 if (d < minD) { 3129 const push = (minD - d) / 2; 3130 const ux = dx / d, uy = dy / d; 3131 3132 network.moveNode(a, pA.x - ux * push, pA.y - uy * push); 3133 network.moveNode(b, pB.x + ux * push, pB.y + uy * push); 3134 } 3135 } 3136 } 3137 } 3138 } 3139 3140 function placeFamilyAroundCenter(network, nodes, centerId, memberIds, radius = 140, startAngle = -Math.PI / 2) { 3141 const centerPos = network.getPositions([centerId])[centerId]; 3142 if (!centerPos) return; 3143 3144 const validMembers = memberIds.filter(id => nodes.get(id) && id !== centerId); 3145 if (!validMembers.length) return; 3146 3147 const totalWeight = validMembers.reduce((acc, id) => { 3148 const n = nodes.get(id); 3149 return acc + (n?.size || 20); 3150 }, 0); 3151 3152 let currentAngle = startAngle; 3153
3154 validMembers.forEach(id => { 3155 const n = nodes.get(id); 3156 const weight = (n?.size || 20) / totalWeight; 3157 const angleSpan = weight * 2 * Math.PI; 3158 const angle = currentAngle + angleSpan / 2; 3159 3160 const x = centerPos.x + Math.cos(angle) * radius; 3161 const y = centerPos.y + Math.sin(angle) * radius; 3162 3163 network.moveNode(id, x, y); 3164 currentAngle += angleSpan; 3165 }); 3166 } 3167 3168 function arrangeInCircle(network, nodes, memberIds, radius = 140, startAngle = -Math.PI / 2, sharedBoundaryNodes = {}) { 3169 const validMembers = memberIds.filter(id => nodes.get(id)); 3170 if (validMembers.length < 2) return; 3171 3172 const pos = network.getPositions(validMembers); 3173 let cx = 0, cy = 0; 3174 validMembers.forEach(id => { 3175 cx += pos[id].x; 3176 cy += pos[id].y; 3177 }); 3178 cx /= validMembers.length; 3179 cy /= validMembers.length; 3180 3181 const total = validMembers.length; 3182 const slotAngles = Array.from({ length: total }, (_, i) => startAngle + (i * 2 * Math.PI / total)); 3183 3184 const normalizeAngle = a => { 3185 let x = a % (2 * Math.PI); 3186 if (x < 0) x += 2 * Math.PI; 3187 return x; 3188 }; 3189 3190 const usedSlots = new Set(); 3191 const slotByNode = new Map(); 3192 3193 // 1) reservar slots para nodos compartidos, colocándolos en el borde deseado 3194 Object.entries(sharedBoundaryNodes).forEach(([nodeId, desiredAngle]) => { 3195 if (!validMembers.includes(nodeId)) return; 3196 3197 const target = normalizeAngle(desiredAngle); 3198 let bestIdx = -1; 3199 let bestDiff = Infinity; 3200
3201 slotAngles.forEach((angle, idx) => { 3202 if (usedSlots.has(idx)) return; 3203 3204 const diffRaw = Math.abs(normalizeAngle(angle) - target); 3205 const diff = Math.min(diffRaw, 2 * Math.PI - diffRaw); 3206 3207 if (diff < bestDiff) { 3208 bestDiff = diff; 3209 bestIdx = idx; 3210 } 3211 }); 3212 3213 if (bestIdx !== -1) { 3214 usedSlots.add(bestIdx); 3215 slotByNode.set(nodeId, slotAngles[bestIdx]); 3216 } 3217 }); 3218 3219 // 2) asignar el resto de nodos a slots libres 3220 const remainingNodes = validMembers.filter(id => !slotByNode.has(id)); 3221 const freeSlots = slotAngles.filter((_, idx) => !usedSlots.has(idx)); 3222 3223 remainingNodes.forEach((id, i) => { 3224 slotByNode.set(id, freeSlots[i]); 3225 }); 3226 3227 // 3) mover nodos 3228 validMembers.forEach(id => { 3229 const angle = slotByNode.get(id); 3230 const x = cx + Math.cos(angle) * radius; 3231 const y = cy + Math.sin(angle) * radius; 3232 network.moveNode(id, x, y); 3233 }); 3234 } 3235 3236 function arrangeAroundSharedNode( 3237 network, 3238 nodes, 3239 memberIds, 3240 sharedNodeId, 3241 radius = 140, 3242 sharedAngle = -Math.PI / 2, 3243 startSweepAngle = -Math.PI / 2, 3244 sweepSize = Math.PI * 1.55 3245 ) { 3246 const validMembers = memberIds.filter(id => nodes.get(id)); 3247 if (validMembers.length < 2) return; 3248 if (!validMembers.includes(sharedNodeId)) return; 3249 3250 const sharedPos = network.getPositions([sharedNodeId])[sharedNodeId]; 3251 if (!sharedPos) return; 3252 3253 // Centro del cÃrculo deducido desde la posición fija del nodo compartido 3254 const cx = sharedPos.x - Math.cos(sharedAngle) * radius; 3255 const cy = sharedPos.y - Math.sin(sharedAngle) * radius; 3256 3257 // El shared node NO cuenta como slot normal 3258 const otherMembers = validMembers.filter(id => id !== sharedNodeId); 3259 const count = otherMembers.length; 3260 if (!count) return; 3261 3262 // Coloca el nodo compartido exactamente donde debe ir 3263 network.moveNode( 3264 sharedNodeId, 3265 cx + Math.cos(sharedAngle) * radius, 3266 cy + Math.sin(sharedAngle) * radius 3267 ); 3268 3269 // Reparte el resto en un arco amplio, dejando libre la zona del shared node 3270 const step = count === 1 ? 0 : sweepSize / (count - 1); 3271 3272 otherMembers.forEach((id, i) => { 3273 const angle = startSweepAngle + step * i; 3274 const x = cx + Math.cos(angle) * radius; 3275 const y = cy + Math.sin(angle) * radius; 3276 network.moveNode(id, x, y); 3277 }); 3278 } 3279 3280 function placeVillafrancaAlbaClusters(network) { 3281 const dukeId = "José Ãlvarez de Toledo, Duke of Alba"; 3282 const duchessId = "MarÃa Teresa de Silva, XIII Duchess of Alba"; 3283 const marquisId = "Francisco Ãlvarez de Toledo, XII Marquis of Villafranca"; 3284 const widowId = "MarÃa Antonia Gonzaga, Marchioness of Villafranca (widow)"; 3285 const mariaTomasaId = "MarÃa Tomasa Palafox, Marchioness of Villafranca"; 3286 3287 const ids = [dukeId, duchessId, marquisId, widowId, mariaTomasaId]; 3288 if (ids.some(id => !nodes.get(id))) return; 3289 3290 const sharedPos = network.getPositions([mariaTomasaId])[mariaTomasaId]; 3291 if (!sharedPos) return; 3292 3293 const radius = 82; 3294 const sharedAngle = 0; // MarÃa Tomasa mira hacia Montijo, borde derecho 3295 const cx = sharedPos.x - Math.cos(sharedAngle) * radius; 3296 const cy = sharedPos.y - Math.sin(sharedAngle) * radius; 3297 3298 const ordered = [ 3299 mariaTomasaId, // 0º derecha 3300 widowId, // 72º arriba-dcha 3301 dukeId, // 144º arriba-izda 3302 duchessId, // 216º abajo-izda 3303 marquisId // 288º abajo-dcha 3304 ]; 3305 3306 ordered.forEach((id, i) => { 3307 const angle = i * (2 * Math.PI / ordered.length); 3308 network.moveNode( 3309 id, 3310 cx + Math.cos(angle) * radius, 3311 cy + Math.sin(angle) * radius 3312 ); 3313 }); 3314 } 3315
3316 function placeFedericoSatelliteClusters(network) { 3317 const goyaId = "Francisco de Goya"; 3318 const federicoId = "Federico de Madrazo"; 3319 3320 if (!nodes.get(goyaId) || !nodes.get(federicoId)) return; 3321 3322 const goyaPos = network.getPositions([goyaId])[goyaId]; 3323 const federicoPos = network.getPositions([federicoId])[federicoId]; 3324 if (!goyaPos || !federicoPos) return; 3325 3326 // ========================================================= 3327 // COURT PAINTERS 3328 // Entre Goya y Federico, pero algo más pegado a Goya. 3329 // ========================================================= 3330 const courtRadius = 120; // increased from 90 for wider circle without overlaps 3331 const courtCenter = { 3332 x: goyaPos.x + (federicoPos.x - goyaPos.x) * 0.52, // before: 0.40 3333 y: goyaPos.y + (federicoPos.y - goyaPos.y) * 0.38 // before: 0.30 3334 }; 3335 3336 const courtAngles = { 3337 "Francisco Bayeu": Math.PI * 1.26, 3338 "Mariano Salvador Maella": Math.PI * 1.00, 3339 "Giambattista Tiepolo": Math.PI * 0.78, 3340 "Giandomenico Tiepolo": Math.PI * 0.58, 3341 "Vicente López": Math.PI * 0.30, 3342 "Anton Raphael Mengs": Math.PI * 0.10, 3343 "AgustÃn Esteve": Math.PI * 1.66 3344 }; 3345 3346 [ 3347 "Francisco Bayeu", 3348 "Mariano Salvador Maella", 3349 "Giambattista Tiepolo", 3350 "Giandomenico Tiepolo", 3351 "Vicente López", 3352 "Anton Raphael Mengs", 3353 "AgustÃn Esteve" 3354 ].forEach(id => { 3355 if (!nodes.get(id)) return; 3356 const angle = courtAngles[id]; 3357 network.moveNode( 3358 id, 3359 courtCenter.x + Math.cos(angle) * courtRadius, 3360 courtCenter.y + Math.sin(angle) * courtRadius 3361 ); 3362 }); 3363 3364 // ========================================================= 3365 // MADRAZO-CARDERERA GROUP 3366 // Arriba-derecha de Federico, no encima del cÃrculo familiar. 3367 // ========================================================= 3368 const mcRadius = 160; 3369 const mcCenter = { 3370 x: federicoPos.x + 300, 3371 y: federicoPos.y - 150 3372 }; 3373 3374 const mcMembers = [ 3375 "ValentÃn Carderera", 3376 "Eugenio Ochoa", 3377 "Santiago Masarnau", 3378 "Vicente Masarnau", 3379 "Carlos Luis de Ribera" 3380 ]; 3381 3382 const mcStep = (2 * Math.PI) / mcMembers.length; 3383 mcMembers.forEach((id, i) => { 3384 if (!nodes.get(id)) return; 3385 const angle = -Math.PI / 2 + i * mcStep; 3386 network.moveNode( 3387 id, 3388 mcCenter.x + Math.cos(angle) * mcRadius, 3389 mcCenter.y + Math.sin(angle) * mcRadius 3390 ); 3391 }); 3392 3393 // ========================================================= 3394 // PRINT SPECIALISTS 3395 // Igual que antes. 3396 // ========================================================= 3397 const piotId = "Eugène Piot"; 3398 if (nodes.get(piotId)) { 3399 const piotPos = network.getPositions([piotId])[piotId]; 3400 if (piotPos) { 3401 const psMembers = [ 3402 "Philippe Burty", 3403 "Paul Lefort", 3404 "Charles Blanc", 3405 "Paul Mantz" 3406 ].filter(id => nodes.get(id)); 3407 3408 const psRadius = 88; 3409 const psCenter = { 3410 x: piotPos.x + 34, 3411 y: piotPos.y + 72 3412 }; 3413 3414 const psAngles = [ 3415 Math.PI * 1.14, 3416 Math.PI * 0.76, 3417 Math.PI * 0.38, 3418 Math.PI * 0.04 3419 ]; 3420 3421 psMembers.forEach((id, i) => { 3422 const angle = psAngles[i]; 3423 network.moveNode( 3424 id, 3425 psCenter.x + Math.cos(angle) * psRadius, 3426 psCenter.y + Math.sin(angle) * psRadius 3427 ); 3428 }); 3429 } 3430 } 3431 } 3432 3433 function placeMadrazoFamilyCluster(network) { 3434 // Properly arrange Madrazo family in a circle 3435 const cfg = RADIAL_CLUSTERS["MADRAZO_FAMILY"]; 3436 if (!cfg || !cfg.members) return; 3437 3438 arrangeInCircle( 3439 network, 3440 nodes, 3441 cfg.members,
3442 cfg.radius || 260, 3443 cfg.startAngle ?? (-Math.PI / 2), 3444 cfg.sharedBoundaryNodes || {} 3445 ); 3446 } 3447 3448 function placeIlustradosCluster(network) { 3449 const cfg = RADIAL_CLUSTERS["ILUSTRADOS_CLUSTER"]; 3450 if (!cfg || !cfg.members) return; 3451 3452 // Arrange in circle first 3453 arrangeInCircle( 3454 network, 3455 nodes, 3456 cfg.members, 3457 cfg.radius || 150, 3458 cfg.startAngle ?? (-Math.PI / 2), 3459 cfg.sharedBoundaryNodes || {} 3460 ); 3461 3462 // Apply vertical offset if specified 3463 if (cfg.centerYOffset) { 3464 const memberIds = cfg.members.filter(id => nodes.get(id)); 3465 const pos = network.getPositions(memberIds); 3466 memberIds.forEach(id => { 3467 const p = pos[id]; 3468 network.moveNode(id, p.x, p.y + cfg.centerYOffset); 3469 }); 3470 } 3471 } 3472 3473 function placeGoyaFamilyCluster(network) { 3474 const cfg = RADIAL_CLUSTERS["GOYA_FAMILY"]; 3475 if (!cfg || !cfg.members) return; 3476 3477 // Arrange in circle first 3478 arrangeInCircle( 3479 network, 3480 nodes, 3481 cfg.members, 3482 cfg.radius || 85, 3483 cfg.startAngle ?? (-Math.PI / 2), 3484 cfg.sharedBoundaryNodes || {} 3485 ); 3486 3487 // Apply vertical offset if specified 3488 if (cfg.centerYOffset) { 3489 const memberIds = cfg.members.filter(id => nodes.get(id)); 3490 const pos = network.getPositions(memberIds); 3491 memberIds.forEach(id => { 3492 const p = pos[id]; 3493 network.moveNode(id, p.x, p.y + cfg.centerYOffset); 3494 }); 3495 } 3496 } 3497 3498 function placeGodoyPositioning(network) { 3499 // Position Godoy between his two wives: Chinchón (1st wife) and Tudó (2nd wife) 3500 const godoyId = "Manuel Godoy"; 3501 const chinchonId = "XV Countess of Chinchón"; 3502 const tudoId = "Josefa Tudó"; 3503 3504 const ids = [godoyId, chinchonId, tudoId].filter(id => nodes.get(id)); 3505 if (ids.length < 3 || !nodes.get(godoyId) || !nodes.get(chinchonId) || !nodes.get(tudoId)) return; 3506 3507 const pos = network.getPositions(ids); 3508 const chinchonPos = pos[chinchonId]; 3509 const tudoPos = pos[tudoId]; 3510 3511 if (!chinchonPos || !tudoPos) return; 3512 3513 // Place Godoy between them (average position, slightly offset) 3514 const godoyX = (chinchonPos.x + tudoPos.x) / 2 + 15; 3515 const godoyY = (chinchonPos.y + tudoPos.y) / 2 - 20; 3516 network.moveNode(godoyId, godoyX, godoyY); 3517 } 3518 3519 function placeMonacoGroup(network) { 3520 // Position Louis-Philippe next to Montpensier, and Montpensier next to Infanta MarÃa Luisa 3521 const louisId = "Louis Philippe I"; 3522 const montpensierId = "Prince Antoine, Duke of Montpensier"; 3523 const infantaId = "Infanta Luisa Fernanda de Borbón"; 3524 3525 const ids = [louisId, montpensierId, infantaId].filter(id => nodes.get(id)); 3526 if (ids.length < 3 || !nodes.get(infantaId)) return; 3527 3528 const pos = network.getPositions(ids); 3529 const infantaPos = pos[infantaId]; 3530 3531 if (!infantaPos) return; 3532 3533 // Position Montpensier to the left of Infanta (below) 3534 network.moveNode(montpensierId, infantaPos.x - 70, infantaPos.y + 40); 3535 3536 // Position Louis-Philippe to the left of Montpensier 3537 const montpensierNewPos = network.getPositions([montpensierId])[montpensierId]; 3538 if (montpensierNewPos) { 3539 network.moveNode(louisId, montpensierNewPos.x - 70, montpensierNewPos.y - 35); 3540 } 3541 } 3542 3543 function placeMontpensierBridge(network) { 3544 const infantaId = "Infanta Luisa Fernanda de Borbón"; 3545 const louisId = "Louis Philippe I"; 3546 const montpensierId = "Prince Antoine, Duke of Montpensier"; 3547 3548 if (!nodes.get(infantaId) || !nodes.get(louisId) || !nodes.get(montpensierId)) return; 3549 3550 const infantaPos = network.getPositions([infantaId])[infantaId]; 3551 if (!infantaPos) return; 3552 3553 network.moveNode(louisId, infantaPos.x + 88, infantaPos.y - 34); 3554 network.moveNode(montpensierId, infantaPos.x + 92, infantaPos.y + 40); 3555 } 3556 3557 function placeEstevesPair(network) { 3558 const agustinId = "AgustÃn Esteve"; 3559 const rafaelId = "Rafael Esteve"; 3560 3561 if (!nodes.get(agustinId) || !nodes.get(rafaelId)) return; 3562 3563 const agustinPos = network.getPositions([agustinId])[agustinId]; 3564 if (!agustinPos) return; 3565 3566 network.moveNode(rafaelId, agustinPos.x - 54, agustinPos.y + 36); 3567 } 3568 3569 function placeCloseMasterStudentPairs(network) { 3570 // Keep key master-student pairs close together despite cluster boundaries 3571 const masterStudentPairs = [ 3572 ["Ernest Meissonier", "Eduardo Zamacois", 90], 3573 // Add more as needed 3574 ]; 3575
3576 masterStudentPairs.forEach(([masterId, studentId, distance]) => { 3577 if (!nodes.get(masterId) || !nodes.get(studentId)) return; 3578 3579 const masterPos = network.getPositions([masterId])[masterId]; 3580 if (!masterPos) return; 3581 3582 // Position student to the right of master 3583 network.moveNode(studentId, masterPos.x + distance, masterPos.y); 3584 }); 3585 } 3586 3587 function placeCloseRelativesToGoya(network) { 3588 // Rosario and Leocadia are level 1-2 with Goya, keep them very close 3589 const goyaId = "Francisco de Goya"; 3590 if (!nodes.get(goyaId)) return; 3591 3592 const goyaPos = network.getPositions([goyaId])[goyaId]; 3593 if (!goyaPos) return; 3594 3595 // Position Rosario and Leocadia above and to the right (accessible from Goya's perspective) 3596 if (nodes.get("Rosario Weiss Zorrilla")) { 3597 network.moveNode("Rosario Weiss Zorrilla", goyaPos.x - 100, goyaPos.y - 150); 3598 } 3599 3600 if (nodes.get("Leocadia Zorrilla y Galarza")) { 3601 network.moveNode("Leocadia Zorrilla y Galarza", goyaPos.x - 100, goyaPos.y - 100); 3602 } 3603 } 3604 3605 function placeGeorgeSandConnections(network) { 3606 // George Sand's level-1 connections should be close to her despite cluster boundaries 3607 const georgeSandId = "George Sand"; 3608 if (!nodes.get(georgeSandId)) return; 3609 3610 const georgeSandPos = network.getPositions([georgeSandId])[georgeSandId]; 3611 if (!georgeSandPos) return; 3612 3613 // Level-1 connections to keep close 3614 const connections = [ 3615 { id: "Eugène Delacroix", distance: 110, angle: -Math.PI / 4 }, // NW 3616 { id: "Frédéric Chopin", distance: 95, angle: -Math.PI / 3 }, // upper left 3617 { id: "Alfred de Musset", distance: 100, angle: -Math.PI * 0.6 }, // left 3618 { id: "Prosper Mérimée", distance: 110, angle: -Math.PI * 0.7 } // lower left 3619 ]; 3620 3621 connections.forEach(conn => { 3622 if (!nodes.get(conn.id)) return; 3623 const x = georgeSandPos.x + Math.cos(conn.angle) * conn.distance; 3624 const y = georgeSandPos.y + Math.sin(conn.angle) * conn.distance; 3625 network.moveNode(conn.id, x, y); 3626 }); 3627 } 3628 3629 function placeBourbonCluster(network) { 3630 const members = [ 3631 "Carlos III", 3632 "Carlos IV", 3633 "MarÃa Luisa de Parma", 3634 "Fernando VII", 3635 "Infanta Luisa Fernanda de Borbón", 3636 "Luis de Borbón", 3637 "MarÃa Teresa de Vallabriga", 3638 "Isabel II", 3639 "MarÃa Cristina de Borbón-Dos Sicilias" 3640 ].filter(id => nodes.get(id)); 3641 3642 const chinchonId = "XV Countess of Chinchón"; 3643 if (!nodes.get(chinchonId) || !members.length) return; 3644 3645 const godoyId = "Manuel Godoy"; 3646 const radius = 150; 3647 3648 // Calculate circle center from OTHER members (not Chinchón) 3649 const memberPos = network.getPositions(members); 3650 let cx = 0, cy = 0; 3651 members.forEach(id => { 3652 cx += memberPos[id].x; 3653 cy += memberPos[id].y; 3654 }); 3655 cx /= members.length; 3656 cy /= members.length; 3657 3658 // Get Godoy's angle from this center 3659 let godoyAngle = 0; 3660 if (nodes.get(godoyId)) { 3661 const godoyPos = network.getPositions([godoyId])[godoyId]; 3662 godoyAngle = Math.atan2(godoyPos.y - cy, godoyPos.x - cx); 3663 } 3664 3665 // Place Chinchón OUTSIDE the circle on the Godoy-facing side 3666 const chinchonRadius = radius + 80; // extend beyond circle toward Godoy 3667 network.moveNode( 3668 chinchonId, 3669 cx + Math.cos(godoyAngle) * chinchonRadius, 3670 cy + Math.sin(godoyAngle) * chinchonRadius 3671 ); 3672 3673 // Arrange other members evenly around the core circle 3674 const angleStep = (2 * Math.PI) / members.length; 3675 let currentAngle = -Math.PI / 2; // start at top 3676
3677 members.forEach(id => { 3678 network.moveNode( 3679 id, 3680 cx + Math.cos(currentAngle) * radius, 3681 cy + Math.sin(currentAngle) * radius 3682 ); 3683 currentAngle += angleStep; 3684 }); 3685 } 3686 3687 function getClusterNodeIds(cfg, nodes) { 3688 return [...new Set( 3689 [ 3690 ...(cfg.center && nodes.get(cfg.center) ? [cfg.center] : []), 3691 ...((cfg.members || []).filter(id => nodes.get(id))) 3692 ] 3693 )]; 3694 } 3695 3696 function getClusterCenterPos(network, nodes, cfg) { 3697 const ids = getClusterNodeIds(cfg, nodes); 3698 if (!ids.length) return null; 3699 3700 if (cfg.center && nodes.get(cfg.center)) { 3701 return network.getPositions([cfg.center])[cfg.center]; 3702 } 3703 3704 const pos = network.getPositions(ids); 3705 let cx = 0, cy = 0; 3706 ids.forEach(id => { 3707 cx += pos[id].x; 3708 cy += pos[id].y; 3709 }); 3710 3711 return { 3712 x: cx / ids.length, 3713 y: cy / ids.length 3714 }; 3715 } 3716 3717 function pushOutsidersFromClusters(network, nodes, clusters, defaultPadding = 70) { 3718 const allIds = nodes.getIds().filter(id => !String(id).startsWith('ANCHOR__')); 3719 const pos = network.getPositions(allIds); 3720 3721 Object.entries(clusters).forEach(([clusterId, cfg]) => { 3722 const centerPos = getClusterCenterPos(network, nodes, cfg); 3723 if (!centerPos) return; 3724 3725 const clusterIds = new Set(getClusterNodeIds(cfg, nodes)); 3726 const baseRadius = cfg.radius || 150; 3727 const padding = cfg.padding ?? defaultPadding; 3728 const halo = baseRadius + padding; 3729 3730 allIds.forEach(id => { 3731 if (clusterIds.has(id)) return; 3732 3733 // no expulsar un nodo compartido que pertenece a este clúster 3734 const belongsHere = Array.isArray(nodeClusterMap[id]) && nodeClusterMap[id].includes(clusterId); 3735 if (belongsHere) return; 3736 3737 const p = pos[id]; 3738 if (!p) return; 3739 3740 const outsider = nodes.get(id); 3741 const outsiderHalo = Math.max(18, (getNodeHalo(outsider) || 18) * 0.35); 3742 3743 const dx = p.x - centerPos.x; 3744 const dy = p.y - centerPos.y; 3745 const d = Math.hypot(dx, dy) || 1; 3746 3747 const minD = halo + outsiderHalo; 3748 3749 if (d < minD) { 3750 const push = minD - d; 3751 const ux = dx / d; 3752 const uy = dy / d; 3753 network.moveNode(id, p.x + ux * push, p.y + uy * push); 3754 } 3755 }); 3756 }); 3757 } 3758 3759 function separateClusters(network, nodes, clusters, passes = 16, baseGap = 110, sharedGap = 36) { 3760 const entries = Object.entries(clusters); 3761 3762 for (let pass = 0; pass < passes; pass++) { 3763 let movedAny = false; 3764 3765 for (let i = 0; i < entries.length; i++) { 3766 for (let j = i + 1; j < entries.length; j++) { 3767 const [clusterAId, cfgA] = entries[i]; 3768 const [clusterBId, cfgB] = entries[j]; 3769 3770 const idsA = getClusterNodeIds(cfgA, nodes); 3771 const idsB = getClusterNodeIds(cfgB, nodes); 3772 if (!idsA.length || !idsB.length) continue; 3773 3774 const shared = idsA.filter(id => idsB.includes(id)); 3775 3776 const centerA = getClusterCenterPos(network, nodes, cfgA); 3777 const centerB = getClusterCenterPos(network, nodes, cfgB); 3778 if (!centerA || !centerB) continue; 3779 3780 let dx = centerB.x - centerA.x; 3781 let dy = centerB.y - centerA.y; 3782 let d = Math.hypot(dx, dy); 3783 3784 if (d < 0.001) { 3785 dx = 1; 3786 dy = 0; 3787 d = 1; 3788 } 3789 3790 const radiusA = (cfgA.radius || 150) + (cfgA.padding || 0); 3791 const radiusB = (cfgB.radius || 150) + (cfgB.padding || 0); 3792 const minD = radiusA + radiusB + (shared.length ? sharedGap : baseGap); 3793 3794 if (d < minD) { 3795 const overlap = minD - d; 3796 const ux = dx / d; 3797 const uy = dy / d; 3798 3799 const movableA = idsA.filter(id => !shared.includes(id)); 3800 const movableB = idsB.filter(id => !shared.includes(id)); 3801 3802 const posA = network.getPositions(movableA); 3803 const posB = network.getPositions(movableB); 3804 3805 movableA.forEach(id => { 3806 const p = posA[id]; 3807 network.moveNode(id, p.x - ux * overlap / 2, p.y - uy * overlap / 2); 3808 }); 3809 3810 movableB.forEach(id => { 3811 const p = posB[id]; 3812 network.moveNode(id, p.x + ux * overlap / 2, p.y + uy * overlap / 2); 3813 }); 3814 3815 movedAny = true; 3816 } 3817 } 3818 } 3819 3820 if (!movedAny) break; 3821 } 3822 } 3823 3824 3825 // âââ Loading progress (vis-network physics) âââ 3826 const loadingEl = document.getElementById('loadingMessage'); 3827 const loadingPct = document.getElementById('loadingProgress'); 3828 3829 network.on('stabilizationProgress', function (params) { 3830 if (!loadingEl || !loadingPct) return; 3831 const pct = Math.max(1, Math.min(100, Math.round((params.iterations / params.total) * 100))); 3832 loadingPct.textContent = pct + '%'; 3833 }); 3834 3835 function loadFullImages() { 3836 const imageUpdates = data.nodes 3837 .filter(node =>
3837 node.image) 3838 .map(node => ({ id: node.id, image: node.image })); 3839 3840 if (imageUpdates.length > 0) { 3841 nodes.update(imageUpdates); 3842 } 3843 } 3844 3845 network.once("stabilizationIterationsDone", function () { 3846 document.getElementById('loadingMessage').style.display = 'none'; 3847 3848 // 1) Carga imágenes 3849 loadFullImages(); 3850 3851 // 2) Apaga fÃsica para fijar posiciones 3852 network.setOptions({ physics: { enabled: false } }); 3853 3854 // 3) Check if we have saved node positions - if so, apply them and skip all positioning 3855 if (Object.keys(nodePositions).length > 0) { 3856 console.log('Applying saved node positions, skipping cluster positioning'); 3857 applySavedNodePositions(); 3858 network.redraw(); // Force immediate redraw 3859 3860 // Trigger hash navigation after a small delay to ensure render 3861 setTimeout(() => { 3862 if (!__defaultNodeInfoHTML) { 3863 __defaultNodeInfoHTML = document.getElementById('nodeInfo').innerHTML; 3864 } 3865 if (typeof handleInitialHash === 'function' && !__hashProcessed) { 3866 console.log('ð¯ Triggering hash navigation after render'); 3867 handleInitialHash(); 3868 } 3869 }, 200); // Increased from 100ms to allow rendering 3870 } else { 3871 // 3) Empujón anti-overlap cuando ya están puestas las imágenes 3872 setTimeout(() => { 3873 if (!window.__didNudgeOnce) { 3874 window.__didNudgeOnce = true; 3875 3876 // 1) Small initial nudge to prevent overlaps 3877 nudgeOverlaps(network, nodes, window.__clusterOf, 20); 3878 3879 3880 network.redraw(); 3881 } 3882 }, 150); 3883 3884 // For cases without saved positions, call hash navigation after a short delay 3885 setTimeout(() => { 3886 if (!__defaultNodeInfoHTML) { 3887 __defaultNodeInfoHTML = document.getElementById('nodeInfo').innerHTML; 3888 } 3889 if (typeof handleInitialHash === 'function' && !__hashProcessed) { 3890 handleInitialHash(); 3891 } 3892 }, 200); 3893 } 3894 3895 // Listen for hash changes (when user clicks links with hash URLs) 3896 window.addEventListener('hashchange', () => { 3897 __hashProcessed = false; 3898 handleInitialHash(); 3899 }); 3900 3901 // Safety fallback: ensure physics is disabled after max time 3902 setTimeout(() => { 3903 network.setOptions({ physics: { enabled: false } }); 3904 console.log('â± Force disabled physics after timeout'); 3905 }, 3000); // Force disable after 3 seconds if stabilization hasn't happened yet 3906 }); 3907 3908 function highlightNeighborhood(nodeId) { 3909 // 1) Obtener edges conectados 3910 const connectedEdges = edges.get({ 3911 filter: edge => !edge._isClusterEdge && (edge.from === nodeId || edge.to === nodeId) 3912 }); 3913 3914 // 2) Conjunto de nodos conectados (vecinos directos) 3915 const connectedNodes = new Set(); 3916 connectedEdges.forEach(edge => { 3917 connectedNodes.add(edge.from === nodeId ? edge.to : edge.from); 3918 }); 3919 3920 // 3) Listas de ids 3921 const allNodeIds = nodes.getIds(); 3922 const nonConnected = allNodeIds.filter( 3923 id => id !== nodeId && !connectedNodes.has(id) 3924 ); 3925 3926 const allEdges = edges.get(); 3927 const connectedEdgeIds = new Set(connectedEdges.map(e => e.id)); 3928 activeEdgeIds = new Set(connectedEdgeIds); // por si lo usas en otros sitios 3929 3930 // 4) Actualizar NODOS en un solo batch 3931 const nodeUpdates = []; 3932 3933 // Nodo clicado: rojo 3934 nodeUpdates.push({ 3935 id: nodeId, 3936 color: { border: 'red' }, 3937 borderWidth: 4, 3938 opacity: 1 3939 }); 3940 3941 // Vecinos: naranja 3942 connectedNodes.forEach(id => { 3943 nodeUpdates.push({ 3944 id, 3945 color: { border: '#ffa500' }, 3946 borderWidth: 3, 3947 opacity: 1 3948 }); 3949 }); 3950 3951 // No conectados: atenuados (pero visibles) 3952 nonConnected.forEach(id => { 3953 nodeUpdates.push({ 3954 id, 3955 opacity: 0.4 3956 }); 3957 }); 3958 3959 nodes.update(nodeUpdates); 3960 3961 // 5) Actualizar EDGES en un solo batch 3962 const edgeUpdates = allEdges.map(edge => { 3963 const isConnected = connectedEdgeIds.has(edge.id); 3964 return {
3965 id: edge.id, 3966 color: { 3967 color: isConnected 3968 ? 'rgba(255, 0, 0, 0.9)' // rojo fuerte para edges del vecindario 3969 : 'rgba(200, 200, 200, 0.15)' // un poco más atenuado el resto 3970 }, 3971 width: isConnected ? 3 : 1.5 3972 }; 3973 }); 3974 3975 edges.update(edgeUpdates); 3976 3977 // 6) Guardar estado para clearHighlights() 3978 lastHighlightedNode = nodeId; 3979 lastHighlightedNodes = Array.from(connectedNodes); 3980 lastNonHighlightedNodes = nonConnected; 3981 } 3982 3983 function pointToSegmentDistance(px, py, x1, y1, x2, y2) { 3984 const vx = x2 - x1, vy = y2 - y1; 3985 const wx = px - x1, wy = py - y1; 3986 const c1 = vx * wx + vy * wy; 3987 if (c1 <= 0) return Math.hypot(px - x1, py - y1); 3988 const c2 = vx * vx + vy * vy; 3989 if (c2 <= c1) return Math.hypot(px - x2, py - y2); 3990 const t = c1 / c2; 3991 const projx = x1 + t * vx, projy = y1 + t * vy; 3992 return Math.hypot(px - projx, py - projy); 3993 } 3994 3995 network.on("click", function (params) { 3996 3997 // --- CLICK EN FONDO: limpiar selección de cluster --- 3998 if (params.nodes.length === 0 && params.edges.length === 0) { 3999 if (selectedClusterId) { 4000 window.clearClusterSelection(); 4001 } 4002 } 4003 4004 // Cerrar "New in" solo cuando se hace clic en nodos o edges 4005 if ((params.nodes.length > 0 || params.edges.length > 0) && typeof showNewInPanel === 'function') { 4006 showNewInPanel(false); 4007 } 4008 4009 // --- NODO --- 4010 if (params.nodes.length > 0) { 4011 const node = nodes.get(params.nodes[0]); 4012 4013 if (!node || node.clusterAnchor) return; 4014 4015 updateNodeURL(node.id); 4016 4017 clearHighlights(); 4018 highlightNeighborhood(node.id); 4019 lastHighlightedNode = node.id; 4020 4021 const degree = edgeCount[node.id] || 0; 4022 let html = `<div class="node-info">`; 4023 4024 // âââ Barra de logos de autoridades junto a la foto (vertical, a la derecha) âââ 4025 { 4026 const ICON_SRC = { 4027 'wikidata': 'images/Wikidata_logo.svg', 4028 'getty ulan': 'images/Getty_logo.jpg', 4029 'bnf': 'images/BnF_logo.jpg', 4030 'rah': 'images/RAH_logo.jpg' 4031 }; 4032 4033 // Merge: objeto authorities + posibles campos sueltos 4034 const authorities = Object.assign({}, node.authorities || {}); 4035 if (node['wikidata']) authorities['wikidata'] = node['wikidata']; 4036 if (node['getty ulan']) authorities['getty ulan'] = node['getty ulan']; 4037 if (node['bnf']) authorities['bnf'] = node['bnf']; 4038 if (node['rah']) authorities['rah'] = node['rah']; 4039 4040 const badges = Object.entries(authorities) 4041 .filter(([_, url]) => url) 4042 .map(([key, url]) => { 4043 const k = String(key).toLowerCase(); 4044 const src = ICON_SRC[k] || ICON_SRC['getty ulan']; 4045 const title = 4046 (k === 'getty ulan' || k === 'ulan' || k === 'getty') ? 'Getty (ULAN)' : 4047 (k === 'bnf' ? 'BnF' : 4048 (k === 'wikidata' ? 'Wikidata' :
4049 (k === 'rah' ? 'RAH' : key))); 4050 // ð Importante: SIN height inline (que pisaba el CSS) 4051 return ` 4052 <a href="${url}" target="_blank" rel="noopener noreferrer" title="${title}" style="display:block;"> 4053 <img src="${src}" alt="${title}"> 4054 </a>`; 4055 }).join(''); 4056 4057 if (node.image) { 4058 html += ` 4059 <div class="photo-with-authorities"> 4060 <img src="${node.image}" alt="${node.id}" class="node-portrait"> 4061 ${badges ? `<div class="authority-badges">${badges}</div>` : ``} 4062 </div> 4063 `; 4064 } else if (badges) { 4065 html += `<div class="authority-badges">${badges}</div>`; 4066 } 4067 4068 // Flag global para no repetir los enlaces en el cuerpo 4069 window.__hasAuthoritiesBar = !!badges; 4070 } 4071 4072 4073 const displayName = 4074 (CURRENT_LANG === 'es' && node['display name es']) 4075 ? node['display name es'] 4076 : node.id; 4077 4078 html += `<h2>${displayName}</h2>`; 4079 4080 const fieldsToShow = [ 4081 { type: "section", label: "Identity and Personal Data" }, 4082 { type: "field", key: "full name", label: "Full name" }, 4083 { type: "field", key: "also known as", label: "Also known as" }, 4084 { type: "field", key: "other names", label: "Other names" }, 4085 { type: "field", key: "pseudonyms", label: "Pseudonyms" }, 4086 { type: "field", key: "life dates", label: "Life dates" }, 4087 { type: "field", key: "sex", label: "Sex" }, 4088 { type: "field", key: "profession", label: "Profession" }, 4089 { type: "field", key: "nationality", label: "Nationality" }, 4090 { type: "field", key: "born in", label: "Born in" }, 4091 { type: "field", key: "dies in", label: "Dies in" }, 4092 4093 { type: "section", label: "Social and Political Identity" }, 4094 { type: "field", key: "aristocratic titles", label: "Aristocratic titles" }, 4095 { type: "field", key: "considered as", label: "Considered as" }, 4096 { type: "field", key: "political views", label: "Political views" }, 4097 { type: "field", key: "military activity", label: "Military activity" }, 4098 { type: "field", key: "participates in", label: "Participates in" }, 4099 { type: "field", key: "ties with slavery/colonialism", label: "Ties with slavery/colonialism" }, 4100 4101 { type: "section", label: "Family and Personal Relationships" }, 4102 { type: "field", key: "parents", label: "Parents" }, 4103 { type: "field", key: "siblings", label: "Siblings" }, 4104 { type: "field", key: "married to", label: "Married to" }, 4105 { type: "field", key: "partners/lovers", label: "Partners/lovers" }, 4106 { type: "field", key: "children", label: "Children" }, 4107 { type: "field", key: "friends", label: "Friends" }, 4108 { type: "field", key: "rivals", label: "Rivals" }, 4109 { type: "field", key: "correspondence", label: "Correspondence" }, 4110 { type: "field", key: "meets", label: "Meets" }, 4111 4112 { type: "section", label: "Education, Profession and Institutions" }, 4113 { type: "field", key: "studies in", label: "Studies in" }, 4114 { type: "field", key: "masters", label: "Masters" }, 4115 { type: "field", key: "influenced by", label: "Influenced by" }, 4116 { type: "field", key: "follower of", label: "Follower of" }, 4117 { type: "field", key: "works as", label: "Works as" }, 4118 { type: "field", key: "works for", label: "Works for" }, 4119 { type: "field", key: "collaborates with", label: "Collaborates with" }, 4120 { type: "field", key: "students", label: "Students" }, 4121 { type: "field", key: "registered in", label: "Registered in" }, 4122 { type: "field", key: "member of", label: "Member of" }, 4123 { type: "field", key: "founder of", label: "Founder of" }, 4124 { type: "field", key: "editor of", label: "Editor of" }, 4125 { type: "field", key: "writes in", label: "Writes in" }, 4126 { type: "field", key: "literary salon or tertulia", label: "Literary salon or tertulia" }, 4127 { type: "field", key: "decorations/awards", label: "Decorations/awards" }, 4128 4129 { type: "section", label: "Artistic Activity, Art Collecting and Patronage" }, 4130 { type: "field", key: "author of", label: "Author of" }, 4131 { type: "field", key: "collection", label: "Collection" }, 4132 { type: "field", key: "art collection", label: "Art collection" }, 4133 { type: "field", key: "collector of", label: "Collector of" }, 4134 { type: "field", key: "sales", label: "Sales" }, 4135 { type: "field", key: "exhibitions", label: "Exhibitions" }, 4136 { type: "field", key: "museum collections", label: "Museum collections" }, 4137 { type: "field", key: "patronage", label: "Patronage" }, 4138 { type: "field", key: "patrons", label: "Patrons" }, 4139 { type: "field", key: "portrayed by", label: "Portrayed by" }, 4140 { type: "field", key: "views on art and cultural heritage", label: "Views on art and cultural heritage" }, 4141 4142 { type: "section", label: "Geographic Presence and Movement" }, 4143 { type: "field", key: "lives in", label: "Lives in" }, 4144 { type: "field", key: "address", label: "Address" }, 4145 { type: "field", key: "trips", label: "Trips" }, 4146 { type: "field", key: "in Spain", label: "In Spain" }, 4147 { type: "field", key: "in Madrid", label: "In Madrid" }, 4148 { type: "field", key: "visits the Prado Museum", label: "Visits the Prado Museum" }, 4149 { type: "field", key: "in France", label: "In France" }, 4150 { type: "field", key: "in Paris", label: "In Paris" }, 4151 { type: "field", key: "in Italy", label: "In Italy" }, 4152 4153 { type: "section", label: "Relationship to Goya and His Work" }, 4154 { type: "field", key: "link to Goya's work", label: "Link to Goya's work" }, 4155 { type: "field", key: "discovers Goya's works", label: "Discovers Goya's works" }, 4156 { type: "field", key: "knows Goya's works", label: "Knows Goya's works" }, 4157 { type: "field", key: "copies after Goya", label: "Copies after Goya" }, 4158 { type: "field", key: "writes about Goya", label: "Writes about Goya" }, 4159 { type: "field", key: "mentions of Goya", label: "Mentions of Goya" }, 4160 { type: "field", key: "commissions Goya with", label: "Commissions Goya with" }, 4161 { type: "field", key: "influence of Goya", label: "Influence of Goya" }, 4162 { type: "field", key: "collector of Goya's works", label: "Collector of Goya's works" }, 4163 { type: "field", key: "disseminates Goya's works", label: "Disseminates Goya's works" }, 4164 { type: "field", key: "shows Goya", label: "Shows Goya" }, 4165 { type: "field", key: "mentions the Prado commentaries", label: "Mentions the Prado commentaries" }, 4166 { type: "field", key: "interest in Spanish art", label: "Interest in Spanish art" }, 4167 { type: "field", key: "copies after Spanish artworks", label: "Copies after Spanish artworks" }, 4168 4169 { type: "section", label: "Curiosities" }, 4170 { type: "field", key: "curiosities", label: "Curiosities" }, 4171 4172 { type: "section", label: "Sources and Documentation" }, 4173 { type: "field", key: "mentioned in the French press", label: "Mentioned in the French press" }, 4174 { type: "field", key: "mentions", label: "Mentions" }, 4175 { type: "field", key: "archives", label: "Archives" }, 4176 { type: "field", key: "bibliography", label: "Bibliography" }, 4177 { type: "field", key: "conferences/seminars", label: "Conferences/seminars" }, 4178 { type: "field", key: "newspaper archive", label: "Newspaper archive" }, 4179 { type: "field", key: "websites", label: "Websites" }, 4180 { type: "field", key: "podcasts", label: "Podcasts" }, 4181 { type: "field", key: "image source", label: "Image source" }, 4182 { type: "field", key: "wikidata", label: "Wikidata" }, 4183 { type: "field", key: "getty ulan", label: "Getty ULAN" }, 4184 { type: "field", key: "bnf", label: "BnF" }, 4185 { type: "field", key: "rah", label: "RAH" }, 4186 { type: "field", key: "added", label: "Added" }, 4187 { type: "field", key: "last_modified", label: "Last modified" }, 4188 ]; 4189 4190 const nodesMapByLabel = {};
4191 nodes.get().forEach(n => nodesMapByLabel[n.label] = n); 4192 4193 fieldsToShow.forEach((field, idx) => { 4194 if (field.type === "section") { 4195 // ð§ Buscar los campos que pertenecen solo a esta sección 4196 const fieldsInThisSection = []; 4197 for (let i = idx + 1; i < fieldsToShow.length; i++) { 4198 if (fieldsToShow[i].type === "section") break; 4199 fieldsInThisSection.push(fieldsToShow[i]); 4200 } 4201 4202 const hasData = fieldsInThisSection.some(f => node[f.key]); 4203 if (hasData) { 4204 html += `<h3 class="section-heading">${t(field.label)}</h3>`; 4205 } 4206 } else if (field.type === "field" && node[field.key]) { 4207 // ð Caso especial: Wikidata / Getty ULAN / BnF 4208 if (field.key === 'wikidata' || field.key === 'getty ulan' || field.key === 'bnf' || field.key === 'rah') { 4209 // Si ya mostramos la barra de logos arriba, NO repetir aquà 4210 if (window.__hasAuthoritiesBar) { 4211 return; // evita duplicado 4212 } 4213 4214 // Si NO hay barra (porque no habÃa imagen o logos), renderiza como enlace con icono 4215 const url = String(node[field.key]).trim(); 4216 4217 let iconClass, linkLabel; 4218 if (field.key === 'wikidata') { 4219 iconClass = 'fa-brands fa-wikipedia-w'; 4220 linkLabel = 'Wikidata'; 4221 } else if (field.key === 'getty ulan') { 4222 iconClass = 'fa-solid fa-landmark'; 4223 linkLabel = 'Getty (ULAN)'; 4224 } else if (field.key === 'bnf') { 4225 iconClass = 'fa-solid fa-book'; 4226 linkLabel = 'BnF'; 4227 } else { // 'rah' 4228 iconClass = 'fa-solid fa-building-columns'; 4229 linkLabel = 'RAH'; 4230 } 4231 4232 html += `<p style="margin-top:0.3rem;"> 4233 <a href="${url}" target="_blank" rel="noopener noreferrer" style="color:#66ccff;"> 4234 <i class="${iconClass}" style="margin-right:6px;"></i>${linkLabel} 4235 </a> 4236 </p>`; 4237 return; // evita el formateo genérico 4238 } 4239 4240 4241 let value = node[field.key]; 4242 let htmlText; 4243 4244 // helper: traduce valores "cortos" tipo direct/acquaintances/etc. 4245 const isProfessionField = (field.key === "profession"); 4246 function translateValue(v) { 4247 if (typeof v !== "string") return v; 4248 4249 const raw = v.trim(); 4250 if (!raw) return raw; 4251 4252 // 1) intento literal (por si ya viene "Poet", "Spanish", etc.) 4253 let out = t(raw); 4254 if (out !== raw) return isProfessionField ? out.toLowerCase() : out; 4255 4256 const capFirst = raw.charAt(0).toUpperCase() + raw.slice(1); 4257 out = t(capFirst); 4258 if (out !== capFirst) return isProfessionField ? out.toLowerCase() : out; 4259 4260 // 3) sin traducción 4261 return isProfessionField ? raw.toLowerCase() : raw; 4262 } 4263 4264 if (Array.isArray(value)) { 4265 const processedItems = value.map(item => { 4266 4267 if (typeof item === "string") { 4268 const translatedItem = translateValue(item); 4269 return `<li>${autoLinkNames(processMarkdownLinks(translatedItem), nodesMap)}</li>`; 4270 } 4271 4272 if (typeof item === "object" && item !== null) { 4273 const u = item.url ? String(item.url).trim() : ""; 4274 4275 // Links (websites) 4276 if (u && !isImageUrl(u)) { 4277 const text = item.label || item.caption || u; 4278 const lab = autoLinkNames(processMarkdownLinks(text), nodesMap); 4279 return `<li><a href="${u}" target="_blank" rel="noopener noreferrer" style="color:#66ccff;">${lab}</a></li>`; 4280 } 4281 4282 // Images 4283 if (u && isImageUrl(u)) { 4284 const caption = item.caption 4285 ? autoLinkNames(processMarkdownLinks(item.caption), nodesMap) 4286 : ""; 4287 return `<li>${caption}${caption ? " " : ""}<img src="${u}" alt="Image" style="max-width:100%; margin-left:0.5rem; vertical-align:middle;"></li>`; 4288 } 4289 } 4290 4291 return null; 4292 }
4292).filter(Boolean); 4293 4294 htmlText = `<ul>${processedItems.join("")}</ul>`; 4295 } else if (typeof value === "object" && value !== null && !Array.isArray(value)) { 4296 // Handle plain objects (e.g., "author of" with nested categories) 4297 const sections = []; 4298 for (const [key, items] of Object.entries(value)) { 4299 if (Array.isArray(items)) { 4300 const processedItems = items.map(item => { 4301 if (typeof item === "string") { 4302 const translatedItem = translateValue(item); 4303 return `<li>${autoLinkNames(processMarkdownLinks(translatedItem), nodesMap)}</li>`; 4304 } 4305 return null; 4306 }).filter(Boolean); 4307 4308 if (processedItems.length > 0) { 4309 sections.push(`<div style="margin-top:0.5rem;"><strong>${key}:</strong><ul>${processedItems.join("")}</ul></div>`); 4310 } 4311 } 4312 } 4313 htmlText = sections.join(""); 4314 } else { 4315 let translatedValue = value; 4316 4317 // Handle professions with sex-specific adaptation (pintor/a -> pintor for male, pintora for female) 4318 if (typeof value === "string" && field.key === "profession") { 4319 translatedValue = value 4320 .split(',') 4321 .map(v => { 4322 v = v.trim(); 4323 // First translate the profession 4324 let translated = translateValue(v); 4325 // Then apply gender-specific adaptation 4326 if (node.sex === "female") { 4327 // For female: "pintor/a" becomes "pintora", "editor/a" becomes "editora" 4328 translated = translated.replace(/(\w+)\/a\b/g, '$1a'); 4329 } else if (node.sex === "male") { 4330 // For male: remove /a - "pintor/a" becomes "pintor" 4331 translated = translated.replace(/\/a\b/g, ''); 4332 } 4333 // else: keep as-is (with /a) if sex is unknown 4334 return translated; 4335 }) 4336 .join(', '); 4337 } else if (typeof value === "string" && field.key === "nationality") { 4338 // Nationality handling (unchanged) 4339 translatedValue = value 4340 .split(',') 4341 .map(v => translateValue(v.trim())) 4342 .join(', '); 4343 } else { 4344 translatedValue = translateValue(value); 4345 } 4346 4347 htmlText = autoLinkNames(processMarkdownLinks(translatedValue), nodesMap); 4348 } 4349 4350 html += `<p style="margin-top:0.3rem;"><strong>${labelI18N(field.label)}:</strong> ${htmlText}</p>`; 4351 } 4352 }); 4353 4354 const connections = []; 4355 edges.get().forEach(edge => { 4356 if (edge._isClusterEdge) return; 4357 if (edge.from === node.id || edge.to === node.id) { 4358 const otherId = edge.from === node.id ? edge.to : edge.from; 4359 if (String(otherId).startsWith('ANCHOR__')) return; 4360 const otherNode = nodes.get(otherId); 4361 if (otherNode) { 4362 connections.push({ id: otherId, name: otherNode.id || otherId || 'Unknown' }); 4363 } 4364 } 4365 }); 4366 4367 const degreeCalc = connections.length; 4368 html += `<p style="margin-top:0.5rem; margin-bottom:0.3rem;"><strong>${t('Connections')}:</strong> ${degreeCalc}</p>`; 4369 html += `<p style="font-size:0.85em; color:#999; margin:0.2rem 0 0.5rem 0;">ð = edge ð¤ = node</p><ul style="margin-top:0.3rem;">`; 4370 4371 connections 4372 .sort((a, b) => (a.name || '').localeCompare(b.name || '')) 4373 .forEach(conn => { 4374 html += `<li style="margin:0.25rem 0; display:flex; align-items:center; gap:0.5rem;"> 4375 <span style="flex-grow:1;"> 4376 <a href="#" style="color:#66ccff; font-weight:bold;" onclick="selectEdgeFromNodes('${node.id}', '${conn.id}'); return false;">${conn.name}</a> 4377 </span> 4378 <a href="#" style="color:#66ccff; font-size:1.1em; text-decoration:none;" onclick="selectEdgeFromNodes('${node.id}', '${conn.id}'); return false;" title="View edge">ð</a> 4379 <a href="#" style="color:#999; font-size:1.1em; text-decoration:none;" onclick="focusNode('${conn.id}'); return false;" title="View node">ð¤</a> 4380 </li>`; 4381 }); 4382 4383 html += `</ul></div>`; 4384 document.getElementById("nodeInfo").innerHTML = html; 4385 addShareButton(node.label); 4386 4387 // recordar esta selección para refrescar en cambios de idioma 4388 window.__lastSelection = { type: 'node', id: node.id }; 4389 4390 } else if (params.edges.length > 0) { 4391 4392 // === Smart selection: elegir el edge correcto entre varios candidatos === 4393 let selectedEdgeId = params.edges[0]; 4394 4395 if (params.edges.length > 1) { 4396 const pointer = (params.pointer && params.pointer.canvas) ? params.pointer.canvas : null; 4397 4398 // 1) Prioriza los edges activos (conectados al nodo resaltado) 4399 let pool = params.edges.filter(id => activeEdgeIds.has(id));
4400 if (pool.length === 0) pool = params.edges.slice(); 4401 4402 // 2) Si sigue habiendo varios, elige el más cercano al cursor 4403 if (pointer) { 4404 let best = pool[0]; 4405 let bestDist = Infinity; 4406 for (const id of pool) { 4407 const eObj = edges.get(id); 4408 if (!eObj) continue; 4409 const pos = network.getPositions([eObj.from, eObj.to]); 4410 const p1 = pos[eObj.from], p2 = pos[eObj.to]; 4411 if (!p1 || !p2) continue; 4412 const d = pointToSegmentDistance(pointer.x, pointer.y, p1.x, p1.y, p2.x, p2.y); 4413 if (d < bestDist) { bestDist = d; best = id; } 4414 } 4415 selectedEdgeId = best; 4416 } 4417 } 4418 4419 // Fuerza la selección de ese edge "mejor" 4420 network.selectEdges([selectedEdgeId]); 4421 // === Fin smart selection === 4422 clearNodeHighlightsOnly(); // limpia el rojo de nodos del edge anterior 4423 4424 const edge = edges.get(selectedEdgeId); 4425 if (!edge) return; 4426 if (edge._isClusterEdge) return; 4427 const fromNode = nodes.get(edge.from); 4428 const toNode = nodes.get(edge.to); 4429 4430 if (fromNode && toNode) { 4431 nodes.update([ 4432 { id: fromNode.id, color: { border: 'red' }, borderWidth: 4 }, 4433 { id: toNode.id, color: { border: 'red' }, borderWidth: 4 } 4434 ]); 4435 lastHighlightedNodes = [fromNode.id, toNode.id]; 4436 } 4437 4438 // â Aquà viene el error: ya declaraste fromNode y toNode arriba 4439 // ð Solución: elimina "const" 4440 const fromNodeMap = nodesMap[edge.from]; 4441 const toNodeMap = nodesMap[edge.to]; 4442 4443 4444 let html = `<div style="display:flex; align-items:center; gap:1rem; padding-bottom:1rem;">`; 4445 4446 if (fromNodeMap?.image) { 4447 html += `<img src="${fromNodeMap.image}" style="max-height:80px;">`; 4448 } 4449 if (toNodeMap?.image) { 4450 html += `<img src="${toNodeMap.image}" style="max-height:80px;">`; 4451 } 4452 4453 html += `</div>`; 4454 4455 // TÃtulo traducible 4456 html += `<h3 class="section-heading">${t('Connection')}</h3>`; 4457 4458 // LÃnea "Between ⦠and â¦" traducible 4459 const betweenLabel = t('Between'); 4460 const andWord = t('and'); 4461 4462 html += `<p><strong>${betweenLabel}:</strong> 4463 <a href="#" style="color:#66ccff; text-transform:none;" onclick="focusNode('${fromNodeMap.id}')">${fromNodeMap.id}</a> 4464 ${andWord} 4465 <a href="#" style="color:#66ccff; text-transform:none;" onclick="focusNode('${toNodeMap.id}')">${toNodeMap.id}</a> 4466 </p>`; 4467 4468 const edgeFields = [ 4469 { key: "connection_level", label: "Type of connection" }, 4470 { key: "strength", label: "Connection Strength" }, 4471 { key: "relationship type", label: "Type of relationship" }, 4472 { key: "know each other since", label: "Know each other since" }, 4473 { key: "correspondence", label: "Correspondence" }, 4474 { key: "they met", label: "They met" }, 4475 { key: "shared", label: "Shared" }, 4476 { key: "they shared", label: "They shared" }, 4477 { key: "interactions", label: "Interactions" }, 4478 { key: "introductions", label: "Introductions" }, 4479 { key: "feuds", label: "Feuds" }, 4480 { key: "common friends", label: "Common friends" }, 4481 { key: "mentions", label: "mentions" }, 4482 { key: "collaborations", label: "Collaborations" }, 4483 { key: "curiosities", label: "Curiosities" }, 4484 { key: "children", label: "Children" }, 4485 { key: "married in", label: "Married in" }, 4486 { key: "link to Goya's work", label: "Link to Goya's work" }, 4487 { key: "link to Goya", label: "Link to Goya" }, 4488 { key: "copies after Goya", label: "Copies after Goya" }, 4489 { key: "copies", label: "Copies" }, 4490 { key: "commissions Goya with", label: "Commissions Goya with" }, 4491 { key: "writes about Goya", label: "Writes about Goya" }, 4492 { key: "mentions of Goya", label: "Mentions of Goya" }, 4493 { key: "quotations of Goya", label: "Quotations of Goya" }, 4494 { key: "influence of Goya", label: "Influence of Goya" }, 4495 { key: "collector of Goya's works", label: "Collector of Goya's works" }, 4496 { key: "disseminates Goya's works", label: "Disseminates Goya's works" }, 4497 { key: "discovers Goya's works", label: "Discovers Goya's works" }, 4498 { key: "knows Goya's works", label: "Knows Goya's works" }, 4499 { key: "mentions the Prado commentaries", label: "Mentions the Prado commentaries" }, 4500 { key: "shows Goya", label: "Shows Goya" }, 4501 { key: "artworks influenced by Goya", label: "Artworks influenced by Goya" }, 4502 { key: "interest in Spanish art", label: "Interest in Spanish art" }, 4503 { key: "artworks bought/sold/given", label: "Artworks bought/sold/given" }, 4504 { key: "artworks bought/sold", label: "Artworks bought/sold" }, 4505 { key: "comparisons", label: "Comparisons" }, 4506 { key: "criticism", label: "Criticism" }, 4507 { key: "rivalry", label: "Rivalry" }, 4508 { key: "employment/patronage", label: "Employment/patronage" }, 4509 { key: "commissions", label: "Commissions" }, 4510 { key: "influence", label: "Influence" }, 4511 { key: "exchanges", label: "Exchanges" }, 4512 { key: "collection", label: "Collection" }, 4513 { key: "patronage", label: "Patronage" }, 4514 { key: "portraits", label: "Portraits" }, 4515 { key: "exhibitions", label: "Exhibitions" }, 4516 { key: "bibliography", label: "Bibliography" }, 4517 { key: "websites", label: "Websites" }, 4518 { key: "podcasts", label: "Podcasts" }, 4519 { key: "newspaper archive", label: "Newspaper archive" }, 4520 { key: "archives", label: "Archives" }, 4521 { key: "added", label: "Added" }, 4522 { key: "last_modified", label: "Last modified" } 4523 ]; 4524 4525 edgeFields.forEach(field => { 4526 if (edge[field.key]) { 4527 let value = edge[field.key]; 4528 let htmlText; 4529 4530 // Debugging: Check the raw value 4531 console.log("Raw value:", value); 4532 4533 if (Array.isArray(value)) { 4534 // â Si es un array de strings u objetos con imagen 4535 const processedItems = value 4536 .map(item => { 4537 4538 // â Caso 1: array de strings 4539 if (typeof item === "string") { 4540 return `<li>${autoLinkNames(processMarkdownLinks(item), nodesMap)}</li>`; 4541 } 4542 4543 // â Caso 2: objetos (y no null) 4544 if (typeof item === "object" && item !== null) { 4545 4546 // â websites: {label, url} 4547 if (item.label && item.url) { 4548 const lab = autoLinkNames(processMarkdownLinks(item.label), nodesMap); 4549 const u = String(item.url).trim(); 4550 return `<li><a href="${u}" target="_blank" rel="noopener noreferrer" style="color:#66ccff;">${lab}</a></li>`; 4551 } 4552 4553 // â portraits/images: {caption, url} 4554 const caption = item.caption 4555 ? autoLinkNames(processMarkdownLinks(item.caption), nodesMap) 4556 : ""; 4557 const url = item.url ? item.url : ""; 4558 return `<li>${caption}${url ? `<img src="${url}" alt="Portrait" style="max-width:100%; margin-left: 0.5rem; vertical-align: middle;">` : ""}</li>`; 4559 } 4560 4561 return null; 4562 }) 4563 .filter(Boolean); 4564 4565 htmlText = `<ul>${processedItems.join("")}</ul>`; 4566 4567 } else { 4568 let translatedValue = value; 4569 4570 if (typeof value === "string") { 4571 translatedValue = value 4572 .split(',') 4573 .map(v => { 4574 const raw = v.trim(); 4575 4576 // 1) intento literal 4577 let out = t(raw); 4578 if (out !== raw) return out; 4579 4580 // 2) intento con mayúscula inicial: "poet" â "Poet" 4581 const cap = raw.charAt(0).toUpperCase() + raw.slice(1); 4582 out = t(cap); 4583 if (out !== cap) return out; 4584 4585 // 3) fallback 4586 return raw; 4587 }) 4588 .join(', '); 4589 } 4590 4591 htmlText = autoLinkNames(processMarkdownLinks(translatedValue), nodesMap); 4592 } 4593 4594 // Debugging: Check the processed HTML 4595 console.log("Processed HTML:", htmlText); 4596 4597 html += `<p style="margin-top:0.3rem;"><strong>${labelI18N(field.label)}:</strong> ${htmlText}</p>`; 4598 } 4599 }); 4600 4601 document.getElementById("nodeInfo").innerHTML = html; 4602 updateEdgeURL(selectedEdgeId); 4603 // recordar esta selección para refrescar en cambios de idioma 4604 window.__lastSelection = { type: 'edge', id: selectedEdgeId }; 4605 } 4606 4607 if (params.nodes.length === 0 && params.edges.length === 0) { 4608 clearHighlights(); 4609 window.__lastSelection = null; // nada seleccionado 4610 4611 if (window.VIS_NETWORK) { 4612 window.VIS_NETWORK.unselectAll(); 4613 } 4614 4615 if (typeof window.showDefaultNodeInfo === 'function') { 4616 window.showDefaultNodeInfo(); 4617 } 4618 4619 return; 4620 } 4621 4622 }); 4623 4624 // ===== SELECT EDGE FROM TWO NODES ===== 4625 window.selectEdgeFromNodes = function (fromNodeId, toNodeId) { 4626 // Find the edge connecting these two nodes 4627 let edgeId = null;
4628 edges.get().forEach(edge => { 4629 if ((edge.from === fromNodeId && edge.to === toNodeId) || 4630 (edge.from === toNodeId && edge.to === fromNodeId)) { 4631 edgeId = edge.id; 4632 } 4633 }); 4634 4635 if (!edgeId) return; 4636 4637 clearHighlights(); 4638 4639 // Get the midpoint of the edge for centering 4640 const fromPos = window.VIS_NETWORK.getPosition(fromNodeId); 4641 const toPos = window.VIS_NETWORK.getPosition(toNodeId); 4642 if (!fromPos || !toPos) return; 4643 4644 const midX = (fromPos.x + toPos.x) / 2; 4645 const midY = (fromPos.y + toPos.y) / 2; 4646 4647 // Move to the edge and select it 4648 window.VIS_NETWORK.moveTo({ 4649 position: { x: midX, y: midY }, 4650 scale: window.VIS_NETWORK.getScale() * 1.2 4651 }); 4652 4653 // Emit click event to trigger edge display logic 4654 setTimeout(() => { 4655 window.VIS_NETWORK.emit('click', { 4656 nodes: [], 4657 edges: [edgeId], 4658 pointer: { DOM: { x: 0, y: 0 }, canvas: { x: 0, y: 0 } } 4659 }); 4660 }, 100); 4661 }; 4662 4663 window.focusNode = function (nodeId) { 4664 clearHighlights(); 4665 4666 const scale = window.VIS_NETWORK.getScale(); 4667 const pos = window.VIS_NETWORK.getPosition(nodeId); 4668 4669 window.VIS_NETWORK.moveTo({ 4670 position: pos, 4671 scale 4672 }); 4673 4674 window.VIS_NETWORK.selectNodes([nodeId]); 4675 4676 setTimeout(() => { 4677 window.VIS_NETWORK.emit('click', { 4678 nodes: [nodeId], 4679 edges: [], 4680 pointer: { DOM: { x: 0, y: 0 }, canvas: { x: 0, y: 0 } } 4681 }); 4682 }, 50); 4683 }; 4684 4685 function showShareModal(label = '') { 4686 const modal = document.getElementById('shareModal'); 4687 const hash = '#' + label.replace(/ /g, '_'); 4688 const shareUrl = `${window.location.origin}${window.location.pathname}${hash}`; 4689 const text = `Check out ${label} on the Goya Network`; 4690 4691 document.getElementById('shareUrl').value = shareUrl; 4692 4693 document.getElementById('twitterShare').href = 4694 `https://twitter.com/intent/tweet?text=${encodeURIComponent(text)}&url=${encodeURIComponent(shareUrl)}`; 4695 4696 document.getElementById('linkedinShare').href = 4697 `https://www.linkedin.com/shareArticle?mini=true&url=${encodeURIComponent(shareUrl)}&title=${encodeURIComponent(text)}`; 4698 4699 document.getElementById('facebookShare').href = 4700 `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(shareUrl)}"e=${encodeURIComponent(text)}`; 4701 4702 modal.style.display = 'flex'; 4703 } 4704 4705 // Crear y añadir botón 4706 function addShareButton(nodeName) { 4707 const shareBtn = document.createElement('button'); 4708 shareBtn.innerHTML = '<i class="fas fa-share-alt"></i> Share'; 4709 shareBtn.style.marginTop = '1rem'; 4710 shareBtn.style.padding = '0.5rem 1rem'; 4711 shareBtn.style.background = '#333'; 4712 shareBtn.style.color = 'white'; 4713 shareBtn.style.border = '1px solid #555'; 4714 shareBtn.style.borderRadius = '4px'; 4715 const label = nodes.get(nodeName)?.label || nodeName; 4716 shareBtn.onclick = () => showShareModal(label); 4717 4718 const nodeInfo = document.getElementById('nodeInfo'); 4719 if (nodeInfo) { 4720 const existingShareBtn = nodeInfo.querySelector('.share-btn'); 4721 if (existingShareBtn) existingShareBtn.remove(); 4722 shareBtn.className = 'share-btn'; 4723 nodeInfo.appendChild(shareBtn); 4724 } 4725 } 4726 4727 function updateNodeURL(nodeId) { 4728 const newUrl = window.location.pathname + makeNodeHash(nodeId); 4729 window.history.pushState({}, '', newUrl); 4730 } 4731 4732 function updateEdgeURL(edgeId) { 4733 const edge = edges.get(edgeId); 4734 if (!edge) return ''; 4735 4736 const fromNode = nodes.get(edge.from); 4737 const toNode = nodes.get(edge.to); 4738 if (!fromNode || !toNode) return ''; 4739 4740 const a = fromNode.id; 4741 const b = toNode.id; 4742 4743 // Ordenar para que el hash sea consistente 4744 const ordered = [a, b].sort((x, y) => 4745 x.localeCompare(y, undefined, { sensitivity: 'base' }) 4746 ); 4747 4748 const hash = '#edge/' + slugifyName(ordered[0]) + '--' + slugifyName(ordered[1]); 4749 const newUrl = window.location.pathname + hash; 4750 window.history.pushState({}, '', newUrl); 4751 } 4752 4753 4754 // Restaura el panel nodeInfo a su estado por defecto (texto + Members list) 4755 window.showDefaultNodeInfo = function () { 4756 const el = document.getElementById('nodeInfo'); 4757 if (!el) return; 4758 4759 // Reconstruir SIEMPRE el panel inicial real.
4760 // No depender de __defaultNodeInfoHTML. 4761 el.innerHTML = ` 4762 <p data-i18n="HELP_TEXT_1">${t('HELP_TEXT_1')}</p> 4763 <p data-i18n="HELP_TEXT_2">${t('HELP_TEXT_2')}</p> 4764 4765 <div id="newNodesSection" style="margin-top: 1rem; position: relative;"> 4766 <div id="newNodesToggle" 4767 class="section-heading" 4768 data-i18n="NEW_NODES_TITLE" 4769 style="cursor:pointer; user-select:none; margin-bottom:0;"> 4770 ${t('NEW_NODES_TITLE')} 4771 </div> 4772 4773 <div id="newNodesDropdown" 4774 style="display:none; margin-top:0.5rem; background:#1a1a1a; border:1px solid #444; border-radius:6px; padding:0.75rem;"> 4775 <div id="newNodesList" style="line-height:1.5;"></div> 4776 </div> 4777 </div> 4778 4779 <div id="membersSection" style="margin-top: 1rem;"> 4780 <div class="section-heading">${t('MEMBERS_TITLE')}</div> 4781 <div id="membersList"></div> 4782 </div> 4783 `; 4784 4785 buildMembersList(data); 4786 buildNewNodesList(data); 4787 4788 // Reenganchar el desplegable de New members 4789 const newNodesToggle = document.getElementById('newNodesToggle'); 4790 const newNodesDropdown = document.getElementById('newNodesDropdown'); 4791 4792 if (newNodesToggle && newNodesDropdown) { 4793 newNodesToggle.onclick = function (e) { 4794 e.stopPropagation(); 4795 const isOpen = newNodesDropdown.style.display === 'block'; 4796 newNodesDropdown.style.display = isOpen ? 'none' : 'block'; 4797 newNodesToggle.classList.toggle('open', !isOpen); 4798 }; 4799 } 4800 4801 window.__lastSelection = null; 4802 4803 if (window.VIS_NETWORK) { 4804 window.VIS_NETWORK.unselectAll(); 4805 } 4806 4807 if (typeof applyUIStrings === 'function') { 4808 applyUIStrings(); 4809 } 4810 }; 4811 4812 // Búsqueda funcional 4813 const searchInput = document.getElementById('searchInput'); 4814 const searchButton = document.querySelector('.search-button'); 4815 4816 // Replace the existing searchButton event listener with this: 4817 searchButton.addEventListener('click', () => { 4818 const query = searchInput.value.trim().toLowerCase(); 4819 if (!query) return; 4820 4821 // Clear previous highlights 4822 clearHighlights(); 4823 4824 // 1. Buscar primero en nodos (prioridad) 4825 let found = data.nodes.find(n => 4826 n.id.toLowerCase() === query || 4827 (typeof n.label === 'string' && n.label.toLowerCase() === query) 4828 ); 4829 4830 // 2. Si no hay coincidencia exacta, buscar por startsWith 4831 if (!found) { 4832 found = data.nodes.find(n => 4833 n.id.toLowerCase().startsWith(query) || 4834 (typeof n.label === 'string' && n.label.toLowerCase().startsWith(query)) 4835 ); 4836 } 4837 4838 // 3. Si aún no, buscar por contiene 4839 if (!found) { 4840 found = data.nodes.find(n => 4841 n.id.toLowerCase().includes(query) || 4842 (typeof n.label === 'string' && n.label.toLowerCase().includes(query)) 4843 ); 4844 } 4845 4846 // 4. Si no está en id o label, buscar en otros campos del nodo 4847 if (!found) { 4848 found = data.nodes.find(n => 4849 Object.entries(n).some(([key, value]) => 4850 typeof value === 'string' && 4851 !key.includes('image') && 4852 !value.includes('PFayos') && 4853 value.toLowerCase().includes(query) 4854 ) 4855 ); 4856 } 4857 4858 // 5. Si aún no hay nodo, buscar en los edges 4859 if (!found) { 4860 const matchingEdge = data.edges.find(edge => 4861 Object.entries(edge).some(([key, value]) => { 4862 if (key === "from" || key === "to" || key === "portraits") return false; 4863 return typeof value === 'string' && 4864 !key.includes('image') && 4865 value.toLowerCase().includes(query); 4866 }) 4867 ); 4868 4869 if (matchingEdge) { 4870 const edge = edges.get().find(e => 4871 e.from === matchingEdge.from && e.to === matchingEdge.to 4872 ); 4873 4874 if (edge) { 4875 network.selectEdges([edge.id]);
4876 network.emit('click', { edges: [edge.id], nodes: [] }); 4877 return; 4878 } 4879 } 4880 } 4881 4882 // 6. Mostrar resultado o alerta 4883 if (found) { 4884 window.search(found.id); 4885 } else { 4886 alert("No match found."); 4887 } 4888 }); 4889 4890 // Add Enter key support 4891 searchInput.addEventListener('keydown', (e) => { 4892 if (e.key === 'Enter') { 4893 e.preventDefault(); 4894 searchButton.click(); 4895 } 4896 }); 4897 4898 window.toggleFullScreen = function () { 4899 const elem = document.documentElement; 4900 if (!document.fullscreenElement) { 4901 elem.requestFullscreen().catch(err => { 4902 alert(`Error trying to enable full-screen mode: ${err.message}`); 4903 }); 4904 } else { 4905 document.exitFullscreen(); 4906 } 4907 } 4908 4909 // Activar fÃsica solo mientras se arrastra un nodo 4910 /*network.on("dragStart", () => { 4911 network.setOptions({ physics: { enabled: true } }); 4912 }); 4913 4914 network.on("dragEnd", () => { 4915 network.setOptions({ physics: { enabled: false } }); 4916 });*/ 4917 4918 window.returnToNetworkHome = function () { 4919 clearHighlights(); 4920 window.__lastSelection = null; 4921 4922 if (window.VIS_NETWORK) { 4923 window.VIS_NETWORK.unselectAll(); 4924 } 4925 4926 if (typeof window.showDefaultNodeInfo === 'function') { 4927 window.showDefaultNodeInfo(); 4928 } 4929 }; 4930 4931 } catch (err) { 4932 console.error("Error cargando o renderizando la red:", err); 4933 } 4934});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.