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