User:Mahir256/syndepgraph.js

From Wikidata
Jump to navigation Jump to search

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Press Ctrl-F5.
/* Used to make interesting SVG-based syntactic dependency graphs generated with {{Syndepgraph}} appear. Examples of such graphs are at [[Wikidata:Lexicographical data/Universal Dependencies]].

To use it, add

    importScript('User:Mahir256/syndepgraph.js');

to your common.js (or load it some other way).

As a more experimental piece of functionality, this can also be used on a lexeme page with 'combines' statements. Once on such a lexeme page, click

    "Show dependency graphs on lexeme page"

in the Tools portion of the sidebar, and the 'combines' statements at the top level of the lexeme will be formed into a graph.

This is currently a one-way transformation; edits to the statements after the graph is generated can only be accomplished after a refresh of the page.

Because this basically does what brat does (https://github.com/nlplab/brat), this script will also be released under the Expat license. */

( function ( mw, $ ) {

var svg_style_text = ""
    + ".text-block-selected { fill:yellow } "
    + ".rel-hidden { visibility:hidden } "
    + ".arc-selected { stroke-width:3px } ";
    + "";

function hideHighlight(toplevel, class1, class2){
    return toggleHighlight(toplevel, class1, class2);
}

function toggleHighlight(toplevel, class1, class2){
    return function(){
        $(".textlayer rect."+class1, toplevel).toggleClass("text-block-selected");
        $(".textlayer rect."+class2, toplevel).toggleClass("text-block-selected");
        $(".arclayer path."+class1+"."+class2, toplevel).toggleClass("arc-selected");
        $(".rellayer text."+class1+"."+class2, toplevel).toggleClass("rel-hidden");
    }
}

function showHighlight(toplevel, class1, class2){
    return function(){
        hideAllHighlight(toplevel)();
        toggleHighlight(toplevel, class1, class2)();
    }
}
function hideAllHighlight(toplevel){
    return function(){
        $(".textlayer rect", toplevel).removeClass("text-block-selected");
        $(".arclayer path", toplevel).removeClass("arc-selected");
        $(".rellayer text", toplevel).addClass("rel-hidden");
    }
}

function newSVG(){return document.createElementNS("http://www.w3.org/2000/svg", "svg");}
function SVGelem(name){return document.createElementNS("http://www.w3.org/2000/svg", name);}
// define useful values
var text_start_x = 24;
var text_end_x_offset = 24;
var text_width = 144;
var text_height = 24;
var text_start_y = 64;
var rel_start_y = 86;
var text_arrow_gap = 4;
var arrow_separation_distance = 18;
var y_upper_limit = 30;
var arrow_startend_difference = 12;
var starting_height = 110;
var starting_width = 190;

var definitions = SVGelem("defs");
var endmarker = SVGelem("marker");
$(endmarker).attr({id:"end_arrow",refX:10,refY:10,markerWidth:20,markerHeight:20,orient:"auto",markerUnits:"userSpaceOnUse",fill:"black"})
var endmarkershape = SVGelem("polyline");
$(endmarkershape).attr({points:"0,0 10,10 0,20"});
$(endmarker).append(endmarkershape);
$(definitions).append(endmarker);
var svgstyle = SVGelem("style");
$(svgstyle).attr({type:"text/css"});
$(svgstyle).html("<![CDATA[" + svg_style_text + "]]>");
$(definitions).append(svgstyle);

$(".syndepgraph").each(function(){
    var other_class = $(this).attr("class").split(' ')[1];
    var emphasized_rel = $(this).attr("data-emphasis");
    var current_svg = newSVG();
    $(current_svg).append(svgstyle);
    $(current_svg).append(definitions);
    $(current_svg).attr({height:starting_height, width:starting_width});
    
    // title
    var titlemark = SVGelem("text");
    $(titlemark).attr({x:text_start_x,y:y_upper_limit-text_arrow_gap,'text-anchor':'right','dominant-baseline':'middle','class':'word'});
    $(titlemark).html($(this).children(':first').html());
    $(current_svg).append(titlemark);

    // textlayer
    var textlayer = SVGelem("g");
    $(textlayer).attr({x:0,y:0,class:"textlayer"});
    relations = []
    $(this).children(":not(:first)").each(function(index){
        var current_textrect = SVGelem("rect");
        $(current_textrect).attr({x:text_start_x+index*text_width,
                                  y:text_start_y,
                                  width:text_width,
                                  height:text_height,
                                  fill:'white',stroke:'black',
                                  class:$(this).attr('class')});
        var current_textelem = SVGelem("text");
        $(current_textelem).attr({x:text_start_x+(index+0.5)*text_width,
                                  y:text_start_y+0.5*text_height,
                                  class:$(this).attr('class'),
                                  'text-anchor':'middle',
                                  'dominant-baseline':'middle'});
        $(current_textelem).html($(this).html());
        $(current_textrect).mouseenter(showHighlight(current_svg, $(this).attr('class'), $(this).attr('data-target')))
                           .mouseleave(hideHighlight(current_svg, $(this).attr('class'), $(this).attr('data-target')));
        $(current_textelem).mouseenter(showHighlight(current_svg, $(this).attr('class'), $(this).attr('data-target')))
                           .mouseleave(hideHighlight(current_svg, $(this).attr('class'), $(this).attr('data-target')));
        $(textlayer).append(current_textrect);
        $(textlayer).append(current_textelem);
        relations.push([$(this).attr('class'),
                        $(this).attr('data-target'),
                        $(this).attr('data-relid'),
                        $(this).attr('data-relname')]);
    });
    $(current_svg).append(textlayer);
    
    // arc and relationship layers
    var arclayer = SVGelem("g"); $(arclayer).attr("class","arclayer");
    var rellayer = SVGelem("g"); $(rellayer).attr("class","rellayer");
    relations.forEach(function(item, index, array){
        // mark root explicitly
        var target_index = item[1].replace('word','')
        if(target_index == '0'){ // todo: split things off into their own methods
            reltext = SVGelem("text");
            $(reltext).attr({x:text_start_x+(0.5+index)*text_width,
                                  y:rel_start_y+0.5*text_height,
                                  class:item[0]+" "+item[1],
                                  'text-anchor':'middle',
                                  'dominant-baseline':'middle'});
            if(item[2] != emphasized_rel)
                $(reltext).addClass('rel-hidden');
            $(reltext).html('<a href="https://www.wikidata.org/wiki/'+item[2]+'">'+item[3]+'</a>');
            $(rellayer).append(reltext);
            return;
        }

        if(item[2] == emphasized_rel){
            $(textlayer).children('rect.'+item[0]).addClass('text-block-selected');
            $(textlayer).children('rect.'+item[1]).addClass('text-block-selected');
        }

        // define more useful values
        var arrow_origin_x = text_start_x+0.5*text_width;
        var startpt = arrow_origin_x+(index)*text_width;
        var endpt = arrow_origin_x+(target_index-1)*text_width;
        if(index > target_index)
            endpt += text_width/4.0;
        else
            endpt -= text_width/4.0;
        
        var arrow_origin_y = text_start_y;
        var arrow_height = arrow_separation_distance*(index+1);

        // build path string
        dstring = "M"+(startpt+text_arrow_gap)+","+arrow_origin_y+"v-"+arrow_height+"h"+(endpt-startpt)+"v"+arrow_height;
        newpath = SVGelem("path")

        // assign path string
        $(newpath).attr({'d':dstring,
                         'marker-end':'url(#end_arrow)',
                         class:item[0]+' '+item[1]}).css({fill:'none',stroke:'black'});
        if(item[2] == emphasized_rel)
            $(newpath).addClass('arc-selected');
        $(arclayer).append(newpath);
        
        // add label for relationship
        reltext = SVGelem("text");
        $(reltext).attr({x:startpt+(endpt-startpt)/2,
                                  y:rel_start_y+0.5*text_height,
                                  class:item[0]+' '+item[1],
                                  'text-anchor':'middle',
                                  'dominant-baseline':'middle'});
        if(item[2] != emphasized_rel)
            $(reltext).addClass('rel-hidden');
        $(reltext).html('<a href="https://www.wikidata.org/wiki/'+item[2]+'">'+item[3]+'</a>');
        $(rellayer).append(reltext);
    });
    var yshift_if_any = text_start_y - arrow_separation_distance*(relations.length+1);
    if(yshift_if_any <= y_upper_limit){
        $(current_svg).attr('height',$(current_svg).attr('height')-yshift_if_any+y_upper_limit);
        $(textlayer).attr('transform','translate(0,'+(y_upper_limit-yshift_if_any)+')');
        $(arclayer).attr('transform','translate(0,'+(y_upper_limit-yshift_if_any)+')');
        $(rellayer).attr('transform','translate(0,'+(y_upper_limit-yshift_if_any)+')');
    }
    var xshift_if_any = text_start_x + ($(this).children().length - 1)*text_width;
    if(xshift_if_any >= starting_width - text_end_x_offset){
        $(current_svg).attr('width',xshift_if_any + text_end_x_offset);
    }
    $(current_svg).append(arclayer);
    $(current_svg).append(rellayer);
    $(this).html(current_svg);
});

var node = mw.util.addPortletLink(
    'p-tb',
    'https://www.wikidata.org/wiki/Wikidata:Lexicographical_data/Universal_Dependencies',
    'Show dependency graphs on lexeme page'
);

$( node ).on( 'click', function ( e ) {

// begin graph on lexeme page
$('.wikibase-entityview-main>.wikibase-statementgrouplistview .wikibase-statementgroupview#P5238').each(function(index0, stmtgroup){
    if($("svg", stmtgroup).length > 0){
        return;
    }
    // collect and arrange information
    var elements = [];
    $('.wikibase-statementview.listview-item', stmtgroup).each(function(index1, stmt){
        var lemmaid = '';
        $('.wikibase-statementview-mainsnak .wikibase-snakview-value a', stmt).each(function(index2,elem){lemmaid = $(elem).attr('href').replace('/wiki/Lexeme:','')});
        var lemmatext = $('.wikibase-statementview-mainsnak .wikibase-snakview-value span', stmt).text();
        var sourceindex = 0;
        var targetindex = 0;
        var relationship = '';
        if($('div.wikibase-snakview-variation-somevaluesnak', stmt).length > 0){
            lemmatext = $('div.wikibase-snakview-variation-somevaluesnak', stmt).text();
        }
        $('.wikibase-snaklistview', stmt).each(function(index2, elem){
            if($('.wikibase-snakview-property a[href="/wiki/Property:P1932"]', elem).length > 0)
                lemmatext = $('.wikibase-snakview-value', elem).html();
            if($('.wikibase-snakview-property a[href="/wiki/Property:P5548"]', elem).length > 0)
                lemmatext = $('.wikibase-snakview-value', elem).text();
            else if($('.wikibase-snakview-property a[href="/wiki/Property:P1545"]', elem).length > 0)
                sourceindex = $('.wikibase-snakview-value', elem).text();
            else if($('.wikibase-snakview-property a[href="/wiki/Property:P9764"]', elem).length > 0)
                targetindex = $('.wikibase-snakview-value', elem).text();
            else if($('.wikibase-snakview-property a[href="/wiki/Property:P9763"]', elem).length > 0)
                relationship = $('.wikibase-snakview-value', elem).html();
        });
        elements.push([sourceindex, lemmaid, lemmatext, targetindex, relationship]);
    });
    elements.sort(function(a,b){
        return a[0] - b[0];
    });

    // now on to the SVG
    var current_svg = newSVG();
    $(current_svg).append(definitions);
    $(current_svg).attr({height:starting_height, width:starting_width});
    
    // title
    var titlemark = SVGelem("text");
    var lemma = '';
    $('.lemma-widget_lemma-value').each(function(index2, current_lemma){
        lemma += '/'+$(current_lemma).text();
    });
    $(titlemark).attr({x:text_start_x,y:y_upper_limit-text_arrow_gap,'text-anchor':'right','dominant-baseline':'middle','class':'word'});
    $(titlemark).html(lemma.substr(1));
    $(current_svg).append(titlemark);

    // textlayer
    var textlayer = SVGelem("g");
    $(textlayer).attr({x:0,y:0,class:"textlayer"});

    elements.forEach(function(item, index, current_array){
        var sourceindex = item[0];
        var lemmaid = item[1];
        var lemmatext = item[2];
        var targetindex = item[3];
        var relationship = item[4];
        var word_class = "word"+sourceindex;
        var target_class = "word"+targetindex;

        var current_textrect = SVGelem("rect");
        $(current_textrect).attr({x:text_start_x+index*text_width,
                                  y:text_start_y,
                                  width:text_width,
                                  height:text_height,
                                  fill:'white',stroke:'black',
                                  class:word_class});
        var current_textelem = SVGelem("text");
        $(current_textelem).attr({x:text_start_x+(index+0.5)*text_width,
                                  y:text_start_y+0.5*text_height,
                                  class:word_class,
                                  'text-anchor':'middle',
                                  'dominant-baseline':'middle'});
        $(current_textelem).html('<a href="https://www.wikidata.org/entity/'+lemmaid+'">'+lemmatext+'</a>');
        $(current_textrect).mouseenter(showHighlight(current_svg, word_class, target_class))
                           .mouseleave(hideHighlight(current_svg, word_class, target_class));
        $(current_textelem).mouseenter(showHighlight(current_svg, word_class, target_class))
                           .mouseleave(hideHighlight(current_svg, word_class, target_class));
        $(textlayer).append(current_textrect);
        $(textlayer).append(current_textelem);
    });
    $(current_svg).append(textlayer);
    
    // arc and relationship layers
    var arclayer = SVGelem("g"); $(arclayer).attr("class","arclayer");
    var rellayer = SVGelem("g"); $(rellayer).attr("class","rellayer");
    elements.forEach(function(item, index, current_array){
        var source_index = item[0];
        var source_class = 'word'+source_index;
        var target_index = item[3];
        var target_class = 'word'+target_index;
        var combined_class = source_class+" "+target_class;
        var relationship_text = item[4];

        // mark root explicitly
        if(target_index == '0'){ // todo: split things off into their own methods
            reltext = SVGelem("text");
            $(reltext).attr({x:text_start_x+(0.5+index)*text_width,
                                  y:rel_start_y+0.5*text_height,
                                  class:combined_class,
                                  'text-anchor':'middle',
                                  'dominant-baseline':'middle'});
            $(reltext).html(relationship_text);
            $(reltext).addClass('rel-hidden');
            $(rellayer).append(reltext);
            return;
        }

        // define more useful values
        var arrow_origin_x = text_start_x+0.5*text_width;
        var startpt = arrow_origin_x+(index)*text_width;
        var endpt = arrow_origin_x+(target_index-1)*text_width;
        if(index > target_index)
            endpt += text_width/4.0;
        else
            endpt -= text_width/4.0;
        
        var arrow_origin_y = text_start_y;
        var arrow_height = arrow_separation_distance*(index+1);

        // build path string
        dstring = "M"+(startpt+text_arrow_gap)+","+arrow_origin_y+"v-"+arrow_height+"h"+(endpt-startpt)+"v"+arrow_height;
        newpath = SVGelem("path")

        // assign path string
        $(newpath).attr({'d':dstring,
                         'marker-end':'url(#end_arrow)',
                         class:combined_class}).css({fill:'none',stroke:'black'});
        $(arclayer).append(newpath);
        
        // add label for relationship
        reltext = SVGelem("text");
        $(reltext).attr({x:startpt+(endpt-startpt)/2,
                                  y:rel_start_y+0.5*text_height,
                                  class:$(newpath).attr('class'),
                                  'text-anchor':'middle',
                                  'dominant-baseline':'middle'});
        $(reltext).html(relationship_text);
        $(reltext).addClass('rel-hidden');
        $(rellayer).append(reltext);
    });
    var yshift_if_any = text_start_y - arrow_separation_distance*(elements.length+2);
    if(yshift_if_any <= y_upper_limit){
        $(current_svg).attr('height',$(current_svg).attr('height')-yshift_if_any+y_upper_limit);
        $(textlayer).attr('transform','translate(0,'+(y_upper_limit-yshift_if_any)+')');
        $(arclayer).attr('transform','translate(0,'+(y_upper_limit-yshift_if_any)+')');
        $(rellayer).attr('transform','translate(0,'+(y_upper_limit-yshift_if_any)+')');
    }
    var xshift_if_any = text_start_x + (elements.length)*text_width;
    if(xshift_if_any >= starting_width - text_end_x_offset){
        $(current_svg).attr('width',xshift_if_any + text_end_x_offset);
    }
    $(current_svg).append(arclayer);
    $(current_svg).append(rellayer);
    $(stmtgroup).html(current_svg);
});
// end graph on lexeme page

    e.preventDefault();

} );

} ( mediaWiki, jQuery ) );