Skip to content

Instantly share code, notes, and snippets.

@gangtao
Last active October 9, 2018 07:35
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gangtao/e053cf9722b64ef8544afa371c2daaee to your computer and use it in GitHub Desktop.
Save gangtao/e053cf9722b64ef8544afa371c2daaee to your computer and use it in GitHub Desktop.
G2 Playground
<div class="container">
<div class="row">
<section>
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="nav-item">
<a href="#step1" class="nav-link active" data-toggle="tab" aria-controls="step1" role="tab" title="Step 1">
Load Data
</a>
</li>
<li role="presentation" class="nav-item">
<a href="#step2" class="nav-link" data-toggle="tab" aria-controls="step2" role="tab" title="Step 2">
Prepare Data
</a>
</li>
<li role="presentation" class="nav-item">
<a href="#step3" class="nav-link disable" data-toggle="tab" aria-controls="step3" role="tab" title="Step 3">
Present Data
</a>
</li>
</ul>
<form role="form">
<div class="tab-content">
<div class="tab-pane active" role="tabpanel" id="step1">
<div class="form-group">
<label for="exampleFormControlSelect1">Data Source Select</label>
<select class="form-control" id="dataSourceSelect">
</select>
</div>
<div class="form-group" id="sourceTableContainer">
</div>
</div>
<div class="tab-pane" role="tabpanel" id="step2">
<p>Transform data with a SQL</p>
<form>
<div class="form-group" id="dataQueryContainer"></div>
<div class="form-group" id="transformTableContainer"></div>
<div class="form-group" id="dataSummaryContainer"></div>
</form>
</div>
<div class="tab-pane" role="tabpanel" id="step3">
<p>Visualize your data</p>
<div class="row">
<div id="grammarContainer" class="col-4">
<div id="facetContainer" class="row">
</div>
<div id="geomContainer" class="row">
</div>
<div id="coordContainer" class="row">
</div>
<div id="geomAttrContainer" class="row">
</div>
</div>
<div id="chartContainer" class="col-8">
<div id="chart"></div>
</div>
</div>
</div>
<div class="clearfix"></div>
</div>
</form>
</section>
</div>
</div>
class SqlTable {
constructor(data) {
this.data = data;
}
async query(sql) {
// following line of code does not run in full page view due to security concern.
//const query_str = sql.replace(/(?<=FROM\s+)\w+/, "CSV(?)");
const query_str = sql.replace("table", "CSV(?)");
return await alasql.promise(query_str, [this.data]);
}
}
// Ajax async request
const request = {
get: url => {
return new Promise((resolve, reject) => {
axios
.get(url)
.then(response => {
resolve({ data: response.data });
})
.catch(error => {
resolve({ data: error });
});
});
}
};
// Load the data
async function loadTable(url) {
// Raw data
let csv = await request.get(url);
return new SqlTable(csv.data);
}
function sanitizeData(jsonArray) {
let newKey;
jsonArray.forEach(function(item) {
for (key in item) {
newKey = key.replace(/\s/g, "").replace(/\./g, "");
if (key != newKey) {
item[newKey] = item[key];
delete item[key];
}
}
});
return jsonArray;
}
function displayData(tableId, data) {
// tricky to clone array
let display_data = JSON.parse(JSON.stringify(data));
display_data = sanitizeData(display_data);
let columns = [];
for (let item in display_data[0]) {
columns.push({ data: item, title: item });
}
$("#" + tableId).DataTable({
data: display_data,
columns: columns,
destroy: true
});
}
function isString(o) {
return typeof o == "string" || (typeof o == "object" && o.constructor === String);
}
function summaryData(data) {
let summary = {};
summary.count = data.length;
summary.fields = [];
for (let p in data[0]) {
let field = {};
field.name = p;
if ( isString(data[0][p]) ) {
field.type = "string";
} else {
field.type = "number";
}
summary.fields.push(field);
}
for (let f of summary.fields) {
if ( f.type == "number" ) {
f.max = d3.max(data, x => x[f.name]);
f.min = d3.min(data, x => x[f.name]);
f.mean = d3.mean(data, x => x[f.name]);
f.median = d3.median(data, x => x[f.name]);
f.deviation = d3.deviation(data, x => x[f.name]);
} else {
f.values = Array.from(new Set(data.map(x => x[f.name])));
}
}
return summary;
}
function formatSummary(data) {
let result = "data summary:\n";
result = result + "count:"+ data.count + "\n";
for(let field of data.fields) {
result = result + JSON.stringify(field) + "\n";
}
return result;
}
function rendorDataTable(containerId, tableId, data) {
$("#" + containerId).empty();
$("#" + containerId).append(
'<table class="table table-sm table-striped compact" style="width:100%" id="' + tableId + '"></table>'
);
displayData(tableId, data);
}
async function updateDataTransform(dataTable) {
const sqlQuery = "SELECT * FROM table";
const data = await dataTable.query(sqlQuery);
const summary = summaryData(data);
updateDataPresenting(data);
$("#dataSummaryContainer").empty();
$("#dataSummaryContainer").append('<pre id="dataSummary"></pre>');
$("#dataSummary").text(formatSummary(summary));
$("#dataQueryContainer").empty();
$("#dataQueryContainer").append('<textarea class="form-control" id="dataQueryTextarea" rows="2"></textarea>');
$("#dataQueryTextarea").text(sqlQuery);
let oldVal = $("#dataQueryTextarea").val();
$("#dataQueryTextarea").on('change keyup paste', async function() {
let currentVal = $(this).val();
if(currentVal == oldVal) {
return; //check to prevent multiple simultaneous triggers
}
oldVal = currentVal;
try {
const queryResult = await dataTable.query(currentVal);
console.log(queryResult);
if ( queryResult.length > 0 ) {
rendorDataTable("transformTableContainer","transformTable", queryResult);
updateDataPresenting(queryResult);
}
} catch (err) {
console.log(err);
}
});
rendorDataTable("transformTableContainer","transformTable", data);
}
function updateDataPresenting(queryResult) {
const chartScriptName = "g2chart";
const geom = ["","point","path","line","area","interval","intervalStack","polygon","edge","schema","heatmap"];
const coord = ["","rect","polar","theta","helix"];
const geom_attributes = ["position","color","size","shape","opacity","label"];
const querySummary = summaryData(queryResult);
const fields = querySummary.fields.map( x => x.name);
// initialize facet selection
$("#facetContainer").empty();
const facetSelectContainer = d3.select("#facetContainer")
.classed("container",true)
.append("ul")
.append("li")
.append("label");
facetSelectContainer.text("facet");
facetSelectContainer.append("br");
const facetSelect = facetSelectContainer.append("select")
.style("width","200px")
.classed("select2 input-sm",true)
.attr("id","facetSelect")
.attr("name","facet");
$('#facetSelect').select2({
data: fields,
theme: "bootstrap",
multiple: true,
maximumSelectionLength: 2
});
$('#facetSelect').on('change',updateChart);
$('#facetSelect').on("select2:select", updateSelect2Order);
// initialize geometry selection
$("#geomContainer").empty();
const geomSelectContainer = d3.select("#geomContainer")
.classed("container",true)
.append("ul")
.append("li")
.append("label");
geomSelectContainer.text("geometry");
geomSelectContainer.append("br");
const geomSelect = geomSelectContainer.append("select")
.style("width","200px")
.classed("select2 input-sm",true)
.attr("id","geomSelect")
.attr("name","geom");
$('#geomSelect').select2({
data: geom,
theme: "bootstrap"
});
$('#geomSelect').on('change',updateChart);
// initialize coord selection
$("#coordContainer").empty();
const coordSelectContainer = d3.select("#coordContainer")
.classed("container",true)
.append("ul")
.append("li")
.append("label");
coordSelectContainer.text("coord");
coordSelectContainer.append("br");
const coordSelect = coordSelectContainer.append("select")
.style("width","200px")
.classed("select2 input-sm",true)
.attr("id","coordSelect")
.attr("name","coord");
$('#coordSelect').select2({
data: coord,
theme: "bootstrap"
});
$('#coordSelect').on('change',updateChart);
// initialize geometry attributes selection
$("#geomAttrContainer").empty();
const attrContainer = d3.select("#geomAttrContainer").classed("container",true).append("ul").selectAll("li").data(geom_attributes)
.enter().append("li").append("label");
attrContainer.text(d=>d);
attrContainer.append("br");
attrContainer.append("select")
.style("width","200px")
.classed("select2 input-sm select2-multiple",true)
.attr("id", d => d+"attr")
.attr("name", d => d);
geom_attributes.map(function(attr){
$('#' + attr + "attr").select2({
data:fields,
multiple:true,
theme: "bootstrap",
});
$('#' + attr + "attr").on("select2:select", updateSelect2Order);
$('#' + attr + "attr").on('change', updateChart);
});
function getFacet(faced, grammarScript) {
let facedType = "list";
let facedScript = ""
grammarScript = grammarScript.replace(new RegExp(chartScriptName, 'g'),"view");
if ( faced.length == 2 ) {
facedType = "rect";
}
let facedFields = faced.join("', '")
facedScript = facedScript + `${ chartScriptName }.facet('${ facedType }', {\n`;
facedScript = facedScript + ` fields: [ '${ facedFields }' ],\n`;
facedScript = facedScript + ` eachView(view) {\n`;
facedScript = facedScript + ` ${ grammarScript };\n`;
facedScript = facedScript + ` }\n`;
facedScript = facedScript + `});\n`;
return facedScript
}
function getGrammar() {
let grammar = {}, grammarScript = chartScriptName + ".";
grammar.geom = $('#geomSelect').val();
grammar.coord = $('#coordSelect').val();
grammar.faced = $('#facetSelect').val();
geom_attributes.map(function(attr){
grammar[attr] = $('#' + attr + "attr").val();
});
grammarScript = grammarScript + grammar.geom + "()";
geom_attributes.map(function(attr){
if (grammar[attr].length > 0) {
grammarScript = grammarScript + "." + attr + "('" + grammar[attr].join("*") + "')";
}
});
if (grammar.coord) {
grammarScript = chartScriptName + "." + "coord('" + grammar.coord + "');\n" + grammarScript;
} else {
grammarScript = grammarScript + ";";
}
if ( grammar.faced ) {
if ( grammar.faced.length == 1 ||
grammar.faced.length == 2 ) {
grammarScript = getFacet(grammar.faced, grammarScript);
}
}
console.log(grammarScript)
return grammarScript;
}
function updateChart() {
const grammer = getGrammar();
try {
$("#chart").empty();
let g2chart = new G2.Chart({
container: 'chart',
height : 600,
forceFit: true
});
g2chart.source(queryResult);
eval(grammer);
g2chart.render();
} catch (err) {
//console.log(err);
}
}
function updateSelect2Order(evt) {
let element = evt.params.data.element;
let $element = $(element);
$element.detach();
$(this).append($element);
$(this).trigger("change");
}
}
$(document).ready(function () {
// Prevent the data table pop warning messages
$.fn.dataTable.ext.errMode = 'none';
const dataSourceList = ["Master", "Iris", "Cars", "Berkeley"];
//Replace this for your data repo
const defaultDataRepo = "https://raw.githubusercontent.com/gangtao/dataplay2/master/package/data/";
const defaultDataPostfix = ".csv";
// init data source list
$("#dataSourceSelect").empty();
d3.select("#dataSourceSelect")
.selectAll("option")
.data(dataSourceList)
.enter()
.append("option")
.text(d => d);
$("#dataSourceSelect").change(async function() {
const selected = $("#dataSourceSelect")
.find(":selected")
.text();
const dataTable = await loadTable( defaultDataRepo + selected + defaultDataPostfix );
const data = await dataTable.query("SELECT * FROM table");
updateDataTransform(dataTable);
rendorDataTable("sourceTableContainer","sourceTable",data);
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://gw.alipayobjects.com/os/antv/assets/g2/3.0.9/g2.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/6.26.0/polyfill.min.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/alasql@0.4"></script>
<script src="https://cdn.datatables.net/1.10.16/js/jquery.dataTables.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.2/js/select2.full.min.js"></script>
body {
background-color: #eee;
margin-top: 10px;
}
section {
width: 100%;
}
#dataSourceSelect {
width: 200px;
}
table.dataTable.compact tbody th, table.dataTable.compact tbody td {
padding: 2px;
font-size: 10px;
}
pre, label, p , .dataTables_paginate, .dataTables_info{
font-size: 10px;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0/css/bootstrap.css" rel="stylesheet" />
<link href="https://cdn.datatables.net/1.10.16/css/dataTables.bootstrap4.min.css" rel="stylesheet" />
<link href="https://cdn.datatables.net/1.10.16/css/jquery.dataTables.min.css" rel="stylesheet" />
<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.2/css/select2.min.css" rel="stylesheet" />
<link href="https://select2.github.io/select2-bootstrap-theme/css/select2-bootstrap.css" rel="stylesheet" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment