CINXE.COM
<!DOCTYPE html> <html><head> <meta name="robots" content="nofollow"> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <link rel="stylesheet" type="text/css" href="resources/css/bootstrap.min.css"> <link rel="stylesheet" type="text/css" href="resources/css/common.css"> <script type="text/javascript" src="resources/js/jquery/jquery-1.10.1.min.js"></script> <script type="text/javascript" src="resources/js/bootstrap.min.js"></script> <script type="text/javascript" src="resources/js/common.js"></script> <!--<script type="text/javascript" src="resources/js/wikidata.js"></script>--> <script src="//d3js.org/d3.v3.min.js"></script> <style type="text/css"> li { padding:1px; margin:1px; } /* d3 styles */ .node circle { fill: #fff; stroke: steelblue; stroke-width: 1.5px; } .node { font: 10px sans-serif; } .link { fill: none; stroke: #ccc; stroke-width: 1.5px; } </style> <script type="text/javascript"> var nodes ; var vars ; var tree ; // d3 var max_depth = 9999999 ; function getUrlVars() { vars = {}; var parts = window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(m,key,value) { vars[key] = value; }); } $(document).ready ( function () { getUrlVars() ; if ( vars.q === undefined || ( vars.p === undefined && vars.rp === undefined ) ) return ; $('#intro').hide() ; $('#loading').html('Loading data...').show() ; var roots = (vars.q+'').replace(/[^0-9,]/g,'').split(',') ; var props = vars.p===undefined?[]:(vars.p+'').replace(/[^0-9,]/g,'').split(',') ; var rprops = vars.rp===undefined?[]:(vars.rp+'').replace(/[^0-9,]/g,'').split(',') ; var pref_lang = vars.lang===undefined ? '' : vars.lang ; var method = vars.method===undefined ? 'list' : vars.method ; max_depth = vars.depth===undefined ? max_depth : vars.depth ; var getprops = [] ; $.each ( props , function ( k , v ) { getprops.push(v) } ) ; $.each ( rprops , function ( k , v ) { getprops.push(v) } ) ; getprops = $.unique(getprops) ; // var query = vars.query || ('tree['+roots.join(',')+']['+props.join(',')+']['+rprops.join(',')+']') ; var sparql = 'SELECT ?item' ; $.each ( getprops , function ( k , v ) { sparql += ' ?V' + v ; } ) ; sparql += ' WHERE { VALUES ?roots {wd:Q' + roots.join(' wd:Q') + '} ' ; if ( props.length > 0 ) { sparql += ' . ?tree0 (wdt:P' + props.join('|wdt:P') + ')* ?item' ; if ( rprops.length > 0 ) sparql += ' . ?tree0 (wdt:P' + rprops.join('|wdt:P') + ')* ?roots }' ; } else { sparql += ' . ?item (wdt:P' + rprops.join('|wdt:P') + ')* ?roots' ; } $.each ( getprops , function ( k , v ) { sparql += ' OPTIONAL { ?item wdt:P' + v + ' ?V' + v + ' }' ; } ) ; sparql += ' }' ; $.get ( 'https://query.wikidata.org/sparql' , { query:sparql, format:'json' } , function ( d ) { nodes = {} ; $.each ( d.results.bindings , function ( dummy , v ) { if ( v.item.type != 'uri' ) return ; var q = v.item.value.replace ( /^.+\/Q/ , 'Q' ) ; nodes[q] = { parents:[] , children:[] } ; } ) ; $.each ( roots , function ( dummy , q ) { nodes['Q'+q].depth = 0 ; } ) ; $.each ( getprops , function ( dummy0 , prop ) { var is_rp = $.inArray(prop,props)!=-1 ? true : false ; $.each ( d.results.bindings , function ( dummy , result ) { if ( typeof result['V'+prop] == 'undefined' ) return ; if ( result['V'+prop].type != 'uri' ) return ; var q1 = result.item.value.replace ( /^.+\/Q/ , 'Q' ) ; var q2 = result['V'+prop].value.replace ( /^.+\/Q/ , 'Q' ) ; if ( typeof nodes[q1] == 'undefined' ) return ; if ( typeof nodes[q2] == 'undefined' ) return ; if ( is_rp ) nodes[q2].parents.push ( q1 ) ; else nodes[q1].parents.push ( q2 ) ; } ) ; } ) ; /* } , 'json' ) ; $.getJSON ( '//wdq.wmflabs.org/api?callback=?' , { q:query , props:getprops.join(',') } , function ( d ) { nodes = {} ; $.each ( d.items , function ( dummy , q ) { q = 'Q'+q ; nodes[q] = { parents:[] , children:[] } ; } ) ; $.each ( roots , function ( dummy , q ) { nodes['Q'+q].depth = 0 ; } ) ; $.each ( getprops , function ( dummy0 , prop ) { if ( undefined === d.props[prop] ) return ; var is_rp = $.inArray(prop,props)!=-1 ? true : false ; $.each ( d.props[prop] , function ( dummy , v ) { if ( v[1] != 'item' ) return ; var q1 = "Q"+v[0] ; var q2 = "Q"+v[2] ; if ( undefined === nodes[q1] ) return ; if ( undefined === nodes[q2] ) return ; if ( is_rp ) nodes[q2].parents.push ( q1 ) ; else nodes[q1].parents.push ( q2 ) ; } ) ; } ) ; */ // Init parents var number_of_nodes = 0 ; $.each ( nodes , function ( q , v ) { number_of_nodes++ ; if ( v.parents.length == 0 ) v.depth = 0 ; // Parent node(s) } ) ; // Calculate node depth and main parent var again = true ; var fallback = false ; while ( again ) { again = false ; var found = false ; $.each ( nodes , function ( q , v ) { if ( v.depth !== undefined ) return ; // Done this one var has_unknown = false ; var max_parent_depth = 0 ; $.each ( v.parents , function ( dummy , q2 ) { if ( nodes[q2].depth !== undefined ) { if ( max_parent_depth < nodes[q2].depth ) { max_parent_depth = nodes[q2].depth ; v.main_parent = q2 ; } } else if ( !fallback ) { has_unknown = true ; again = true ; return false ; } } ) ; if ( !has_unknown ) { found = true ; v.depth = max_parent_depth + 1 ; if ( undefined === v.main_parent ) v.main_parent = v.parents[0] ; nodes[v.main_parent].children.push ( q ) ; } } ) ; if ( !found ) { if ( fallback ) break ; else fallback = true ; } } if ( method == 'list' ) { // Draw tree var unlinked = [] ; var h = '' ; if ( number_of_nodes <= 250 ) h += "<div><a href='" + window.location.href.replace(/&method=[a-z0-9]+/,'') + "&method=d3'>Show this as d3 \"star tree\"</a></div>" ; h += '<ol>' ; $.each ( nodes , function ( q , v ) { if ( v.depth === undefined ) unlinked.push ( q ) ; else if ( v.depth == 0 ) h += getNodeHTML ( q ) ; } ) ; h += '</ol>' ; if ( unlinked.length > 0 ) { h += "<h2>Unlinked items</h2><ol>" ; $.each ( unlinked , function ( dummy , q ) { h += "<li><span q='" + q + "'>" + q + "</span></li>" ; } ) ; h += "</ol>" ; } $('#tree').html ( h ) ; $('#loading').html('Loading labels...').show() ; var itemlist = [] ; $.each ( nodes , function ( q , v ) { itemlist.push(q) } ) ; $.post ( './get_item_names.php' , { items:itemlist.join(','), lang:pref_lang } , function ( d ) { $.each ( d.i2n , function ( q , label ) { if ( typeof label != 'string' ) label = q ; $('#'+q).html ( label ) ; } ) ; $('#loading').hide() ; } , 'json' ) ; } else if ( method == 'd3' ) { var h = "<div><a href='" + window.location.href.replace(/&method=[a-z0-9]+/,'') + "&method=list'>Show this as list</a></div>" ; $('#tree').html ( h ) ; var num_roots = 0 ; $.each ( nodes , function ( q , v ) { if ( v.depth == 0 ) num_roots++ ; } ) ; var min_root = 0 ; if ( num_roots > 0 ) { // Todo min_root = -1 ; } var itemlist = [] ; $.each ( nodes , function ( q , v ) { itemlist.push(q) } ) ; $('#loading').html('Loading labels...').show() ; $.post ( './get_item_names.php' , { items:itemlist.join(','), lang:pref_lang } , function ( d ) { var d3o ; $.each ( nodes , function ( q , v ) { if ( v.depth == 0 ) d3o = nodes2d3 ( q , nodes , d.i2n ) ; } ) ; $('#loading').hide() ; render_d3 ( d3o ) ; } ) ; } } , 'json' ) ; } ) ; function render_d3 ( root ) { var diameter = 960; tree = d3.layout.tree() .size([360, diameter / 2 - 120]) .separation(function(a, b) { return (a.parent == b.parent ? 1 : 2) / a.depth; }); var diagonal = d3.svg.diagonal.radial() .projection(function(d) { return [d.y, d.x / 180 * Math.PI]; }); var svg = d3.select("#d3_tree").append("svg") .attr("width", diameter) .attr("height", diameter - 150) .append("g") .attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")"); var nodes = tree.nodes(root), links = tree.links(nodes); var link = svg.selectAll(".link") .data(links) .enter().append("path") .attr("class", "link") .attr("d", diagonal); var node = svg.selectAll(".node") .data(nodes) .enter().append("g") .attr("class", "node") .attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; }) node.append("circle") .attr("r", 4.5); node.append("text") .attr("dy", ".31em") .attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; }) .attr("transform", function(d) { return d.x < 180 ? "translate(8)" : "rotate(180)translate(-8)"; }) .text(function(d) { return d.name; }) .on('click',function(d){window.open('//www.wikidata.org/wiki/'+d.q,'_blank');}); d3.select(self.frameElement).style("height", diameter - 150 + "px"); } function nodes2d3 ( q , nodes , i2n ) { var ret = { name:i2n[q] , q:q } ; if ( nodes[q].children.length > 0 ) { ret.children = [] ; $.each ( nodes[q].children , function ( dummy , qc ) { ret.children.push ( nodes2d3 ( qc , nodes , i2n ) ) ; } ) ; } return ret ; } function getNodeHTML ( q ) { var col = nodes[q].depth ; if ( col > 15 ) col = 15 ; col = 15 - col ; if ( col < 5 ) col = 5 ; col = col.toString(16); col = col+''+col ; col = '#'+col+col+col ; var h = "" ; h += "<li style='background:"+col+"'><a target='_blank' href='//www.wikidata.org/wiki/" + q + "'><span id='" + q + "'>" + q + "</span></a>" ; // h += " <small>["+nodes[q].depth+"]</small>" ; h += "</li>" ; if ( nodes[q].children.length > 0 && nodes[q].depth < max_depth ) { h += "<ol>" ; $.each ( nodes[q].children , function ( dummy , qc ) { h += getNodeHTML ( qc ) ; } ) ; h += "</ol>" ; } return h ; } </script> </head> <body> <div id="menubar" class="navbar navbar-static-top"><div class="navbar-inner"> <div class="container"> <a class="btn btn-navbar navbar-btn" data-toggle="collapse" data-target=".nav-collapse"> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </a> <a id="toolname" class="brand" href="?">Wikidata generic tree</a> <ul id="toolbar-right" class="nav pull-right"> <li><a href="//tools.wmflabs.org/magnustools">Tools</a></li> <li><a href="//toolserver.org/%7Emagnus">Olde tools</a></li> <li><a href="//bitbucket.org/magnusmanske">Git</a></li> <li><a href="//en.wikipedia.org/wiki/User_talk:Magnus_Manske" id="discuss_link">Talk</a></li> <li><a style="margin:0px;padding:0px;margin-top:4px" href="//tools.wmflabs.org/"><img src="https://upload.wikimedia.org/wikipedia/labs/c/cf/Labslogo_thumb.png" title="Powered by Wikimedia Labs" border="0" width="32px"></a></li> </ul> </div> </div> </div> <div id="main_content" class="container"> <div class="row"> <div id='loading' style='display:none;margin-left:5px;padding:2px;color:white;background:red;float:right'></div> <div id='intro'> <p>This tool can generate trees from Wikidata structures, such as subclasses, administrative divisions, and taxons.</p> <p>Examples: <ul> <li><a href='?q=23112&rp=131'>Places in Cambridgeshire</a></li> <li><a href='?q=5113&p=150&rp=171,273,75,76,77,70,71,74,89'>All birds</a> (takes a few seconds)</li> <li><a href='?q=2221906&rp=279'>Location subclasses</a></li> <li><a href='?q=362&rp=361'>Parts of World War II</a> (quite sparse...)</li> </ul> </p> <p>Options: <ul> <li>For small result sets (less than ~250 items), try the <a href='?q=23112&rp=131&method=d3'>d3 "star tree"</a></li> <li>Add "&lang=de" to get German labels, if available (works for all languages)</li> </ul> </p> </div> <div id='tree'></div> <div id='d3_tree'></div> <div style='margin-top:5px;padding-top:2px;border-top:1px solid #DDDDDD'> <i>Note:</i> This tool is based on <a href='//wdq.wmflabs.org'>Wikidata query</a>, which means it can be a few minutes behind the current Wikidata state.<br/> If an item has multiple parents, it will be sorted under the "deepest" parent, as more specific placements appear to be more useful.<br/> Options : depth=<i>number</i> </div> </div> </div> </body> </html>