Jonas Kress (WMDE) has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/290214

Change subject: [WIP] Introduce MultiDimensionResultBrowser
......................................................................

[WIP] Introduce MultiDimensionResultBrowser

Change-Id: Id1ae7dbf092a35cfe4c97597a42d4616a7f09133
---
M index.html
M style.css
M wikibase/queryService/ui/App.js
M wikibase/queryService/ui/resultBrowser/AbstractResultBrowser.js
A wikibase/queryService/ui/resultBrowser/MultiDimensionResultBrowser.js
5 files changed, 303 insertions(+), 1 deletion(-)


  git pull ssh://gerrit.wikimedia.org:29418/wikidata/query/gui 
refs/changes/14/290214/1

diff --git a/index.html b/index.html
index 48140fb..b2bc045 100644
--- a/index.html
+++ b/index.html
@@ -261,6 +261,7 @@
        <script 
src="wikibase/queryService/ui/resultBrowser/AbstractChartResultBrowser.js"></script>
        <script 
src="wikibase/queryService/ui/resultBrowser/TreeMapResultBrowser.js"></script>
        <script 
src="wikibase/queryService/ui/resultBrowser/BubbleChartResultBrowser.js"></script>
+       <script 
src="wikibase/queryService/ui/resultBrowser/MultiDimensionResultBrowser.js"></script>
        <script src="wikibase/queryService/api/Sparql.js"></script>
        <script src="wikibase/queryService/api/QuerySamples.js"></script>
        <script src="wikibase/queryService/api/Wikibase.js"></script>
diff --git a/style.css b/style.css
index 352ef1f..f848ed8 100644
--- a/style.css
+++ b/style.css
@@ -403,3 +403,44 @@
        display: none;
        padding-left: 30px;
 }
+
+/*
+       MultiDimensionResultBrowser
+*/
+
+
+#query-result .foreground path {
+  fill: none;
+  stroke: #222;
+  stroke-opacity: 0.4;
+  pointer-events: none;
+  stroke-width: 1px;
+}
+ 
+#query-result .axis .title {
+  font-size: 8px;
+  font-weight: bold;
+  text-transform: uppercase;
+  transform: rotate(-12deg) translate(-5px,-6px);
+}
+
+#query-result .axis line,
+#query-result .axis path {
+  fill: none;
+  stroke: #000;
+  stroke-width: 1px;
+}
+
+#query-result .brush .extent {
+  fill-opacity: .3;
+  stroke: #fff;
+  stroke-width: 1px;
+}
+
+#query-result pre {
+  width: 900px;
+  margin: 10px 30px;
+  tab-size: 21;
+  font-size: 12px;
+  overflow: auto;
+}
diff --git a/wikibase/queryService/ui/App.js b/wikibase/queryService/ui/App.js
index 8d5579d..868a0ef 100644
--- a/wikibase/queryService/ui/App.js
+++ b/wikibase/queryService/ui/App.js
@@ -108,6 +108,13 @@
                        class: 'TreeMapResultBrowser',
                        object: null,
                        $element: null
+               },
+               MultiDimension: {
+                       icon: 'th',
+                       label: 'MultiDimension',
+                       class: 'MultiDimensionResultBrowser',
+                       object: null,
+                       $element: null
                }
        };
 
diff --git a/wikibase/queryService/ui/resultBrowser/AbstractResultBrowser.js 
b/wikibase/queryService/ui/resultBrowser/AbstractResultBrowser.js
index 3943dfa..891af78 100644
--- a/wikibase/queryService/ui/resultBrowser/AbstractResultBrowser.js
+++ b/wikibase/queryService/ui/resultBrowser/AbstractResultBrowser.js
@@ -71,7 +71,7 @@
                                }
                                self.processVisitors( field, key );
 
-                               cb( field, key, row );
+                               cb( field, key, row, rowNum );
                        } );
                } );
        };
diff --git 
a/wikibase/queryService/ui/resultBrowser/MultiDimensionResultBrowser.js 
b/wikibase/queryService/ui/resultBrowser/MultiDimensionResultBrowser.js
new file mode 100644
index 0000000..827b482
--- /dev/null
+++ b/wikibase/queryService/ui/resultBrowser/MultiDimensionResultBrowser.js
@@ -0,0 +1,253 @@
+var wikibase = wikibase || {};
+wikibase.queryService = wikibase.queryService || {};
+wikibase.queryService.ui = wikibase.queryService.ui || {};
+wikibase.queryService.ui.resultBrowser = 
wikibase.queryService.ui.resultBrowser || {};
+window.mediaWiki = window.mediaWiki || {};
+
+wikibase.queryService.ui.resultBrowser.MultiDimensionResultBrowser = ( 
function( $, d3, window ) {
+       'use strict';
+
+       var TIME_DATATYPE = 'http://www.w3.org/2001/XMLSchema#dateTime';
+       /**
+        * A result browser for multi dimensional data
+        *
+        * @class 
wikibase.queryService.ui.resultBrowser.MultiDimensionResultBrowser
+        * @license GNU GPL v2+
+        *
+        * @author Jonas Kress
+        *
+        * @constructor
+        */
+       function SELF() {
+       }
+
+       SELF.prototype = new 
wikibase.queryService.ui.resultBrowser.AbstractChartResultBrowser();
+
+       /**
+        * Draw browser to the given element
+        *
+        * @param {jQuery} $element to draw at
+        */
+       SELF.prototype.draw = function( $element ) {
+
+               var $graph = $( '<div>' ).appendTo( $element );
+               var $table = $( '<pre>' ).appendTo( $element );
+
+               var dimensions = {}, data = [];
+               var f = this._getFormatter();
+               this._iterateResult( function( field, key, row, rowNum ) {
+
+                       if ( !data[rowNum] ) {
+                               data[rowNum] = {};
+                       }
+
+                       data[rowNum][key] = field && field.value || 'undefined';
+                       if ( field && field.type && field.type === 
TIME_DATATYPE ) {
+                               data[rowNum][key] = new Date( field.value 
).toLocaleDateString();
+                       }
+
+                       if ( !dimensions[key] ) {
+                               var d = {
+                                       key: key,
+                                       description: key,
+                                       type: 'STRING'
+                               };
+                               if ( f.isNumber( field ) ) {
+                                       d.type = 'NUMBER';
+
+                               }
+                               if ( field && field.type && field.type === 
TIME_DATATYPE ) {
+                                       d.type = 'DATE';
+
+                               }
+                               dimensions[key] = d;
+                       }
+
+               } );
+
+               this._draw( $graph, $table, data, _.values( dimensions ) );
+       };
+
+       SELF.prototype._draw = function( $graph, $table, data, dimensions ) {
+               // source: http://bl.ocks.org/syntagmatic/94be812f8b410ae29ee2
+
+               var margin = {
+                       top: 50,
+                       right: 80,
+                       bottom: 20,
+                       left: 200
+               }, width = $( window ).width() - margin.left - margin.right, 
height = $( window ).height() -
+                               margin.top - margin.bottom;
+
+               var types = {
+                       'NUMBER': {
+                               key: 'Number',
+                               coerce: function( d ) {
+                                       return +d;
+                               },
+                               extent: d3.extent,
+                               within: function( d, extent ) {
+                                       return extent[0] <= d && d <= extent[1];
+                               },
+                               defaultScale: d3.scale.linear().range( [
+                                               height, 0
+                               ] )
+                       },
+                       'STRING': {
+                               key: 'String',
+                               coerce: String,
+                               extent: function( data ) {
+                                       return data.sort();
+                               },
+                               within: function( d, extent, dim ) {
+                                       return extent[0] <= dim.scale( d ) && 
dim.scale( d ) <= extent[1];
+                               },
+                               defaultScale: d3.scale.ordinal().rangePoints( [
+                                               0, height
+                               ] )
+                       },
+                       'DATE': {
+                               key: 'Date',
+                               coerce: function( d ) {
+                                       return new Date( d );
+                               },
+                               extent: d3.extent,
+                               within: function( d, extent ) {
+                                       return extent[0] <= d && d <= extent[1];
+                               },
+                               defaultScale: d3.time.scale().range( [
+                                               0, height
+                               ] )
+                       }
+               };
+
+               $.each( dimensions, function() {
+                       this.type = types[this.type];
+               } );
+
+               var x = d3.scale.ordinal().domain( dimensions.map( function( 
dim ) {
+                       return dim.key;
+               } ) ).rangePoints( [
+                               0, width
+               ] );
+
+               var line = d3.svg.line().defined( function( d ) {
+                       return !isNaN( d[1] );
+               } );
+
+               var yAxis = d3.svg.axis().orient( 'left' );
+
+               var svg = d3.select( $graph[0] ).append( 'svg' ).attr( 'width',
+                               width + margin.left + margin.right ).attr( 
'height',
+                               height + margin.top + margin.bottom ).append( 
'g' ).attr( 'transform',
+                               'translate(' + margin.left + ',' + margin.top + 
')' );
+
+               var output = d3.select( $table[0] ).append( 'pre' );
+
+               var foreground = svg.append( 'g' ).attr( 'class', 'foreground' 
);
+
+               var axes = svg.selectAll( '.axis' ).data( dimensions 
).enter().append( 'g' ).attr( 'class',
+                               'axis' ).attr( 'transform', function( d ) {
+                       return 'translate(' + x( d.key ) + ')';
+               } );
+
+               data.forEach( function( d ) {
+                       dimensions.forEach( function( p ) {
+                               d[p.key] = p.type.coerce( d[p.key] );
+                       } );
+               } );
+
+               function draw( d ) {
+                       return line( dimensions.map( function( dim ) {
+                               return [
+                                               x( dim.key ), dim.scale( 
d[dim.key] )
+                               ];
+                       } ) );
+               }
+
+               function brushstart() {
+                       d3.event.sourceEvent.stopPropagation();
+               }
+
+               // Handles a brush event, toggling the display of foreground 
lines.
+               function brush() {
+                       var actives = dimensions.filter( function( p ) {
+                               return !p.brush.empty();
+                       } ), extents = actives.map( function( p ) {
+                               return p.brush.extent();
+                       } );
+
+                       var selected = [];
+
+                       d3.selectAll( '.foreground path' ).style( 'display', 
function( d ) {
+                               if ( actives.every( function( dim, i ) {
+                                       // test if point is within extents for 
each active brush
+                                       return dim.type.within( d[dim.key], 
extents[i], dim );
+                               } ) ) {
+                                       selected.push( d );
+                                       return null;
+                               }
+                               return 'none';
+                       } );
+
+                       //output.text( d3.tsv.format( selected ) );
+               }
+
+               // type/dimension default setting happens here
+               dimensions.forEach( function( dim ) {
+                       if ( !( 'domain' in dim ) ) {
+                               // detect domain using dimension type's extent 
function
+                               dim.domain = d3.functor( dim.type.extent )( 
data.map( function( d ) {
+                                       return d[dim.key];
+                               } ) );
+
+                               // TODO - this line only works because the data 
encodes data with integers
+                               // Sorting/comparing should be defined at the 
type/dimension level
+                               dim.domain.sort( function( a, b ) {
+                                       return a - b;
+                               } );
+                       }
+                       if ( !( 'scale' in dim ) ) {
+                               // use type's default scale for dimension
+                               dim.scale = dim.type.defaultScale.copy();
+                       }
+                       dim.scale.domain( dim.domain );
+               } );
+
+               foreground.selectAll( 'path' ).data( data ).enter().append( 
'path' ).attr( 'd', draw )
+                               .style( 'stroke', function( d ) {
+                                       return '#6ac';
+                               } );
+
+               axes.append( 'g' ).attr( 'class', 'axis' ).each( function( d ) {
+                       var renderAxis = 'axis' in d ? d.axis.scale( d.scale ) 
// custom axis
+                       : yAxis.scale( d.scale ); // default axis
+                       d3.select( this ).call( renderAxis );
+               } ).append( 'text' ).attr( 'class', 'title' ).attr( 
'text-anchor', 'start' ).text(
+                               function( d ) {
+                                       return 'description' in d ? 
d.description : d.key;
+                               } );
+
+               // Add and store a brush for each axis.
+               axes.append( 'g' ).attr( 'class', 'brush' ).each(
+                               function( d ) {
+                                       d3.select( this ).call(
+                                                       d.brush = 
d3.svg.brush().y( d.scale ).on( 'brushstart', brushstart )
+                                                                       .on( 
'brush', brush ) );
+                               } ).selectAll( 'rect' ).attr( 'x', -8 ).attr( 
'width', 16 );
+
+               //output.text( d3.tsv.format( data ) );
+
+       };
+
+       /**
+        * Checks whether the browser can draw the given result
+        *
+        * @return {boolean}
+        */
+       SELF.prototype.isDrawable = function() {
+               return true;
+       };
+
+       return SELF;
+}( jQuery, d3, window ) );

-- 
To view, visit https://gerrit.wikimedia.org/r/290214
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Id1ae7dbf092a35cfe4c97597a42d4616a7f09133
Gerrit-PatchSet: 1
Gerrit-Project: wikidata/query/gui
Gerrit-Branch: master
Gerrit-Owner: Jonas Kress (WMDE) <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to