superset-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From maximebeauche...@apache.org
Subject [incubator-superset] branch master updated: Add event-flow visualization (#3102)
Date Fri, 21 Jul 2017 23:29:29 GMT
This is an automated email from the ASF dual-hosted git repository.

maximebeauchemin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 40d9e15  Add event-flow visualization (#3102)
40d9e15 is described below

commit 40d9e15126121b96fbc1a90e10f6721eb5b47942
Author: Chris Williams <williaster@users.noreply.github.com>
AuthorDate: Fri Jul 21 16:29:25 2017 -0700

    Add event-flow visualization (#3102)
    
    * [event-flow] add event flow visualizaton type from @data-ui/event-flow.
    
    * [event-flow] update vis thumbnail
    
    * [event-flow] update row limit label, remove duplicate chart controls
    
    * [dependencies] add @data-ui/event-flow 0.0.2
    
    * [linting] fix multiple imports
    
    * [deps] bump mapbox-gl and react-map-gl to fix build
    
    * [event-flow] bump to 0.0.3 for es2015 + stage-0 babel presets
    
    * [deps] revert mapbox version bumps
    
    * [event-flow] update png, bump to newest version, address reviewer comments, add min
event count form.
    
    * [event-flow] pin version
    
    * [event-flow][spec] add test for coveralls
    
    * [event-flow] revert spec
---
 .../assets/images/viz_thumbnails/event_flow.png    | Bin 0 -> 108626 bytes
 .../assets/javascripts/explore/stores/controls.jsx |  20 ++++++-
 .../assets/javascripts/explore/stores/visTypes.js  |  46 +++++++++++++++-
 superset/assets/package.json                       |   1 +
 superset/assets/visualizations/EventFlow.jsx       |  61 +++++++++++++++++++++
 superset/assets/visualizations/main.js             |   1 +
 superset/assets/visualizations/treemap.css         |  24 ++++----
 superset/assets/visualizations/treemap.js          |   1 +
 superset/viz.py                                    |  30 ++++++++++
 9 files changed, 170 insertions(+), 14 deletions(-)

diff --git a/superset/assets/images/viz_thumbnails/event_flow.png b/superset/assets/images/viz_thumbnails/event_flow.png
new file mode 100644
index 0000000..4576529
Binary files /dev/null and b/superset/assets/images/viz_thumbnails/event_flow.png differ
diff --git a/superset/assets/javascripts/explore/stores/controls.jsx b/superset/assets/javascripts/explore/stores/controls.jsx
index 794c9ee..1330693 100644
--- a/superset/assets/javascripts/explore/stores/controls.jsx
+++ b/superset/assets/javascripts/explore/stores/controls.jsx
@@ -659,7 +659,7 @@ export const controls = {
     label: 'Entity',
     default: null,
     validators: [v.nonEmpty],
-    description: 'This define the element to be plotted on the chart',
+    description: 'This defines the element to be plotted on the chart',
     mapStateToProps: state => ({
       choices: (state.datasource) ? state.datasource.gb_cols : [],
     }),
@@ -1273,5 +1273,23 @@ export const controls = {
     hidden: true,
     description: 'The number of seconds before expiring the cache',
   },
+
+  order_by_entity: {
+    type: 'CheckboxControl',
+    label: 'Order by entity id',
+    description: 'Important! Select this if the table is not already sorted by entity id,
' +
+    'else there is no guarantee that all events for each entity are returned.',
+    default: true,
+  },
+
+  min_leaf_node_event_count: {
+    type: 'SelectControl',
+    freeForm: false,
+    label: 'Minimum leaf node event count',
+    default: 1,
+    choices: formatSelectOptionsForRange(1, 10),
+    description: 'Leaf nodes that represent fewer than this number of events will be initially
' +
+    'hidden in the visualization',
+  },
 };
 export default controls;
diff --git a/superset/assets/javascripts/explore/stores/visTypes.js b/superset/assets/javascripts/explore/stores/visTypes.js
index 68991e3..bdebf07 100644
--- a/superset/assets/javascripts/explore/stores/visTypes.js
+++ b/superset/assets/javascripts/explore/stores/visTypes.js
@@ -1,5 +1,4 @@
 import { D3_TIME_FORMAT_OPTIONS } from './controls';
-
 import * as v from '../validators';
 
 export const sections = {
@@ -890,6 +889,51 @@ const visTypes = {
       },
     },
   },
+
+  event_flow: {
+    label: 'Event flow',
+    requiresTime: true,
+    controlPanelSections: [
+      {
+        label: 'Event definition',
+        controlSetRows: [
+          ['entity'],
+          ['all_columns_x'],
+          ['row_limit'],
+          ['order_by_entity'],
+          ['min_leaf_node_event_count'],
+        ],
+      },
+      {
+        label: 'Additional meta data',
+        controlSetRows: [
+          ['all_columns'],
+        ],
+      },
+    ],
+    controlOverrides: {
+      entity: {
+        label: 'Column containing entity ids',
+        description: 'e.g., a "user id" column',
+      },
+      all_columns_x: {
+        label: 'Column containing event names',
+        validators: [v.nonEmpty],
+        default: control => (
+          control.choices && control.choices.length > 0 ?
+            control.choices[0][0] : null
+        ),
+      },
+      row_limit: {
+        label: 'Event count limit',
+        description: 'The maximum number of events to return, equivalent to number of rows',
+      },
+      all_columns: {
+        label: 'Meta data',
+        description: 'Select any columns for meta data inspection',
+      },
+    },
+  },
 };
 
 export default visTypes;
diff --git a/superset/assets/package.json b/superset/assets/package.json
index 5d5022d..ff4c161 100644
--- a/superset/assets/package.json
+++ b/superset/assets/package.json
@@ -38,6 +38,7 @@
   },
   "homepage": "https://github.com/airbnb/superset#readme",
   "dependencies": {
+    "@data-ui/event-flow": "0.0.4",
     "babel-register": "^6.24.1",
     "bootstrap": "^3.3.6",
     "brace": "^0.10.0",
diff --git a/superset/assets/visualizations/EventFlow.jsx b/superset/assets/visualizations/EventFlow.jsx
new file mode 100644
index 0000000..110f4a7
--- /dev/null
+++ b/superset/assets/visualizations/EventFlow.jsx
@@ -0,0 +1,61 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+
+import {
+  App,
+  withParentSize,
+  cleanEvents,
+  TS,
+  EVENT_NAME,
+  ENTITY_ID,
+} from '@data-ui/event-flow';
+
+/*
+ * This function takes the slice object and json payload as input and renders a
+ * responsive <EventFlow /> component using the json data.
+ */
+function renderEventFlow(slice, json) {
+  const container = document.querySelector(slice.selector);
+  const hasData = json.data && json.data.length > 0;
+
+  // the slice container overflows ~80px in explorer, so we have to correct for this
+  const isExplorer = (/explore/).test(window.location.pathname);
+
+  const ResponsiveVis = withParentSize(({
+    parentWidth,
+    parentHeight,
+    ...rest
+  }) => (
+    <App
+      width={parentWidth}
+      height={parentHeight - (isExplorer ? 80 : 0)}
+      {...rest}
+    />
+  ));
+
+  // render the component if we have data, otherwise render a no-data message
+  let Component;
+  if (hasData) {
+    const userKey = json.form_data.entity;
+    const eventNameKey = json.form_data.all_columns_x;
+
+    // map from the Superset form fields to <EventFlow />'s expected data keys
+    const accessorFunctions = {
+      [TS]: datum => new Date(datum.__timestamp), // eslint-disable-line no-underscore-dangle
+      [EVENT_NAME]: datum => datum[eventNameKey],
+      [ENTITY_ID]: datum => String(datum[userKey]),
+    };
+
+    const dirtyData = json.data;
+    const cleanData = cleanEvents(dirtyData, accessorFunctions);
+    const minEventCount = slice.formData.min_leaf_node_event_count;
+
+    Component = <ResponsiveVis data={cleanData} initialMinEventCount={minEventCount} />;
+  } else {
+    Component = <div>Sorry, there appears to be no data</div>;
+  }
+
+  ReactDOM.render(Component, container);
+}
+
+module.exports = renderEventFlow;
diff --git a/superset/assets/visualizations/main.js b/superset/assets/visualizations/main.js
index 68abddf..a02f508 100644
--- a/superset/assets/visualizations/main.js
+++ b/superset/assets/visualizations/main.js
@@ -32,5 +32,6 @@ const vizMap = {
   word_cloud: require('./word_cloud.js'),
   world_map: require('./world_map.js'),
   dual_line: require('./nvd3_vis.js'),
+  event_flow: require('./EventFlow.jsx'),
 };
 export default vizMap;
diff --git a/superset/assets/visualizations/treemap.css b/superset/assets/visualizations/treemap.css
index c385780..2fdcdc7 100644
--- a/superset/assets/visualizations/treemap.css
+++ b/superset/assets/visualizations/treemap.css
@@ -1,43 +1,43 @@
-text {
+.treemap text {
   pointer-events: none;
 }
 
-.grandparent text {
+.treemap .grandparent text {
   font-weight: bold;
 }
 
-rect {
+.treemap rect {
   fill: none;
   stroke: #fff;
 }
 
-rect.parent,
-.grandparent rect {
+.treemap rect.parent,
+.treemap .grandparent rect {
   stroke-width: 2px;
 }
 
-rect.parent {
+.treemap rect.parent {
     pointer-events: none;
 }
 
-.grandparent rect {
+.treemap .grandparent rect {
   fill: #eee;
 }
 
-.grandparent:hover rect {
+.treemap .grandparent:hover rect {
   fill: #aaa;
 }
 
-.children rect.parent,
-.grandparent rect {
+.treemap .children rect.parent,
+.treemap .grandparent rect {
   cursor: pointer;
 }
 
-.children rect.parent {
+.treemap .children rect.parent {
   fill: #bbb;
   fill-opacity: .5;
 }
 
-.children:hover rect.child {
+.treemap .children:hover rect.child {
   fill: #bbb;
 }
diff --git a/superset/assets/visualizations/treemap.js b/superset/assets/visualizations/treemap.js
index 1e02593..f728985 100644
--- a/superset/assets/visualizations/treemap.js
+++ b/superset/assets/visualizations/treemap.js
@@ -34,6 +34,7 @@ function treemap(slice, payload) {
         .round(false);
 
     const svg = div.append('svg')
+        .attr('class', 'treemap')
         .attr('width', eltWidth)
         .attr('height', eltHeight);
 
diff --git a/superset/viz.py b/superset/viz.py
index a8cf3bf..1ae42b3 100755
--- a/superset/viz.py
+++ b/superset/viz.py
@@ -1587,6 +1587,35 @@ class MapboxViz(BaseViz):
             "color": fd.get("mapbox_color"),
         }
 
+class EventFlowViz(BaseViz):
+    """A visualization to explore patterns in event sequences"""
+
+    viz_type = "event_flow"
+    verbose_name = _("Event flow")
+    credits = 'from <a href="https://github.com/williaster/data-ui">@data-ui</a>'
+    is_timeseries = True
+
+    def query_obj(self):
+        query = super(EventFlowViz, self).query_obj()
+        form_data = self.form_data
+
+        event_key = form_data.get('all_columns_x')
+        entity_key = form_data.get('entity')
+        meta_keys = [
+            col for col in form_data.get('all_columns') if col != event_key and col != entity_key
+        ]
+
+        query['columns'] = [event_key, entity_key] + meta_keys
+
+        if form_data['order_by_entity']:
+            query['orderby'] = [(entity_key, True)]
+
+        return query
+
+    def get_data(self, df):
+        return df.to_dict(orient="records")
+
+
 
 viz_types_list = [
     TableViz,
@@ -1621,6 +1650,7 @@ viz_types_list = [
     MapboxViz,
     HistogramViz,
     SeparatorViz,
+    EventFlowViz,
 ]
 
 viz_types = OrderedDict([(v.viz_type, v) for v in viz_types_list

-- 
To stop receiving notification emails like this one, please contact
['"commits@superset.apache.org" <commits@superset.apache.org>'].

Mime
View raw message