mirror of https://github.com/apache/superset.git
Merge pull request #94 from mistercrunch/context
Massive js refactor + Dashboard filters
This commit is contained in:
commit
9d0de6c8e7
5
TODO.md
5
TODO.md
|
@ -2,6 +2,11 @@
|
||||||
List of TODO items for Panoramix
|
List of TODO items for Panoramix
|
||||||
|
|
||||||
## Improvments
|
## Improvments
|
||||||
|
* Table description is markdown
|
||||||
|
* Default slice instead of default endpoint
|
||||||
|
* dashboard controller + filters
|
||||||
|
* Color hash in JS
|
||||||
|
* Widget sets ()
|
||||||
* datasource in explore mode could be a dropdown
|
* datasource in explore mode could be a dropdown
|
||||||
* [sql] make "Test Connection" test further
|
* [sql] make "Test Connection" test further
|
||||||
* [druid] Allow for post aggregations (ratios!)
|
* [druid] Allow for post aggregations (ratios!)
|
||||||
|
|
|
@ -1,19 +1,12 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
import csv
|
|
||||||
from datetime import datetime
|
|
||||||
import gzip
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
from subprocess import Popen
|
from subprocess import Popen
|
||||||
|
|
||||||
from flask.ext.script import Manager
|
from flask.ext.script import Manager
|
||||||
from panoramix import app
|
from panoramix import app
|
||||||
from flask.ext.migrate import MigrateCommand
|
from flask.ext.migrate import MigrateCommand
|
||||||
from panoramix import db
|
from panoramix import db
|
||||||
from flask.ext.appbuilder import Base
|
from panoramix import data, utils
|
||||||
from sqlalchemy import Column, Integer, String, Table, DateTime
|
|
||||||
from panoramix import models, utils
|
|
||||||
|
|
||||||
config = app.config
|
config = app.config
|
||||||
|
|
||||||
|
@ -60,314 +53,10 @@ def load_examples(sample):
|
||||||
"""Loads a set of Slices and Dashboards and a supporting dataset """
|
"""Loads a set of Slices and Dashboards and a supporting dataset """
|
||||||
print("Loading examples into {}".format(db))
|
print("Loading examples into {}".format(db))
|
||||||
|
|
||||||
|
print("Loading [World Bank's Health Nutrition and Population Stats]")
|
||||||
BirthNames = Table(
|
data.load_world_bank_health_n_pop()
|
||||||
"birth_names", Base.metadata,
|
print("Loading [Birth names]")
|
||||||
Column("id", Integer, primary_key=True),
|
data.load_birth_names()
|
||||||
Column("state", String(10)),
|
|
||||||
Column("year", Integer),
|
|
||||||
Column("name", String(128)),
|
|
||||||
Column("num", Integer),
|
|
||||||
Column("ds", DateTime),
|
|
||||||
Column("gender", String(10)),
|
|
||||||
Column("sum_boys", Integer),
|
|
||||||
Column("sum_girls", Integer),
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
BirthNames.drop(db.engine)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
BirthNames.create(db.engine)
|
|
||||||
session = db.session()
|
|
||||||
filepath = os.path.join(config.get("BASE_DIR"), 'data/birth_names.csv.gz')
|
|
||||||
with gzip.open(filepath, mode='rt') as f:
|
|
||||||
bb_csv = csv.reader(f)
|
|
||||||
for i, (state, year, name, gender, num) in enumerate(bb_csv):
|
|
||||||
if i == 0 or year < "1965": # jumpy data before 1965
|
|
||||||
continue
|
|
||||||
if num == "NA":
|
|
||||||
num = 0
|
|
||||||
ds = datetime(int(year), 1, 1)
|
|
||||||
db.engine.execute(
|
|
||||||
BirthNames.insert(),
|
|
||||||
state=state,
|
|
||||||
year=year,
|
|
||||||
ds=ds,
|
|
||||||
name=name, num=num, gender=gender,
|
|
||||||
sum_boys=num if gender == 'boy' else 0,
|
|
||||||
sum_girls=num if gender == 'girl' else 0,
|
|
||||||
)
|
|
||||||
if i % 1000 == 0:
|
|
||||||
print("{} loaded out of 82527 rows".format(i))
|
|
||||||
session.commit()
|
|
||||||
session.commit()
|
|
||||||
if sample and i>1000: break
|
|
||||||
print("Done loading table!")
|
|
||||||
print("-" * 80)
|
|
||||||
|
|
||||||
print("Creating database reference")
|
|
||||||
DB = models.Database
|
|
||||||
dbobj = session.query(DB).filter_by(database_name='main').first()
|
|
||||||
if not dbobj:
|
|
||||||
dbobj = DB(database_name="main")
|
|
||||||
print(config.get("SQLALCHEMY_DATABASE_URI"))
|
|
||||||
dbobj.sqlalchemy_uri = config.get("SQLALCHEMY_DATABASE_URI")
|
|
||||||
session.add(dbobj)
|
|
||||||
session.commit()
|
|
||||||
|
|
||||||
print("Creating table reference")
|
|
||||||
TBL = models.SqlaTable
|
|
||||||
obj = session.query(TBL).filter_by(table_name='birth_names').first()
|
|
||||||
if not obj:
|
|
||||||
obj = TBL(table_name = 'birth_names')
|
|
||||||
obj.main_dttm_col = 'ds'
|
|
||||||
obj.default_endpoint = "/panoramix/datasource/table/1/?viz_type=table&granularity=ds&since=100+years&until=now&row_limit=10&where=&flt_col_0=ds&flt_op_0=in&flt_eq_0=&flt_col_1=ds&flt_op_1=in&flt_eq_1=&slice_name=TEST&datasource_name=birth_names&datasource_id=1&datasource_type=table"
|
|
||||||
obj.database = dbobj
|
|
||||||
obj.columns = [
|
|
||||||
models.TableColumn(column_name="num", sum=True, type="INTEGER"),
|
|
||||||
models.TableColumn(column_name="sum_boys", sum=True, type="INTEGER"),
|
|
||||||
models.TableColumn(column_name="sum_girls", sum=True, type="INTEGER"),
|
|
||||||
models.TableColumn(column_name="ds", is_dttm=True, type="DATETIME"),
|
|
||||||
]
|
|
||||||
models.Table
|
|
||||||
session.add(obj)
|
|
||||||
session.commit()
|
|
||||||
obj.fetch_metadata()
|
|
||||||
tbl = obj
|
|
||||||
|
|
||||||
print("Creating some slices")
|
|
||||||
def get_slice_json(slice_name, **kwargs):
|
|
||||||
defaults = {
|
|
||||||
"compare_lag": "10",
|
|
||||||
"compare_suffix": "o10Y",
|
|
||||||
"datasource_id": "1",
|
|
||||||
"datasource_name": "birth_names",
|
|
||||||
"datasource_type": "table",
|
|
||||||
"limit": "25",
|
|
||||||
"flt_col_1": "gender",
|
|
||||||
"flt_eq_1": "",
|
|
||||||
"flt_op_1": "in",
|
|
||||||
"granularity": "ds",
|
|
||||||
"groupby": [],
|
|
||||||
"metric": 'sum__num',
|
|
||||||
"metrics": ["sum__num"],
|
|
||||||
"row_limit": config.get("ROW_LIMIT"),
|
|
||||||
"since": "100 years",
|
|
||||||
"slice_name": slice_name,
|
|
||||||
"until": "now",
|
|
||||||
"viz_type": "table",
|
|
||||||
"where": "",
|
|
||||||
"markup_type": "markdown",
|
|
||||||
}
|
|
||||||
d = defaults.copy()
|
|
||||||
d.update(kwargs)
|
|
||||||
return json.dumps(d, indent=4, sort_keys=True)
|
|
||||||
Slice = models.Slice
|
|
||||||
slices = []
|
|
||||||
|
|
||||||
slice_name = "Girls"
|
|
||||||
slc = session.query(Slice).filter_by(slice_name=slice_name).first()
|
|
||||||
if not slc:
|
|
||||||
slc = Slice(
|
|
||||||
slice_name=slice_name,
|
|
||||||
viz_type='table',
|
|
||||||
datasource_type='table',
|
|
||||||
table=tbl,
|
|
||||||
params=get_slice_json(
|
|
||||||
slice_name, groupby=['name'], flt_eq_1="girl", row_limit=50))
|
|
||||||
session.add(slc)
|
|
||||||
slices.append(slc)
|
|
||||||
|
|
||||||
slice_name = "Boys"
|
|
||||||
slc = session.query(Slice).filter_by(slice_name=slice_name).first()
|
|
||||||
if not slc:
|
|
||||||
slc = Slice(
|
|
||||||
slice_name=slice_name,
|
|
||||||
viz_type='table',
|
|
||||||
datasource_type='table',
|
|
||||||
table=tbl,
|
|
||||||
params=get_slice_json(
|
|
||||||
slice_name, groupby=['name'], flt_eq_1="boy", row_limit=50))
|
|
||||||
session.add(slc)
|
|
||||||
slices.append(slc)
|
|
||||||
|
|
||||||
slice_name = "Participants"
|
|
||||||
slc = session.query(Slice).filter_by(slice_name=slice_name).first()
|
|
||||||
if not slc:
|
|
||||||
slc = Slice(
|
|
||||||
slice_name=slice_name,
|
|
||||||
viz_type='big_number',
|
|
||||||
datasource_type='table',
|
|
||||||
table=tbl,
|
|
||||||
params=get_slice_json(
|
|
||||||
slice_name, viz_type="big_number", granularity="ds",
|
|
||||||
compare_lag="5", compare_suffix="over 5Y"))
|
|
||||||
session.add(slc)
|
|
||||||
slices.append(slc)
|
|
||||||
|
|
||||||
slice_name = "Genders"
|
|
||||||
slc = session.query(Slice).filter_by(slice_name=slice_name).first()
|
|
||||||
if not slc:
|
|
||||||
slc = Slice(
|
|
||||||
slice_name=slice_name,
|
|
||||||
viz_type='pie',
|
|
||||||
datasource_type='table',
|
|
||||||
table=tbl,
|
|
||||||
params=get_slice_json(
|
|
||||||
slice_name, viz_type="pie", groupby=['gender']))
|
|
||||||
session.add(slc)
|
|
||||||
slices.append(slc)
|
|
||||||
|
|
||||||
slice_name = "Gender by State"
|
|
||||||
slc = session.query(Slice).filter_by(slice_name=slice_name).first()
|
|
||||||
if not slc:
|
|
||||||
slc = Slice(
|
|
||||||
slice_name=slice_name,
|
|
||||||
viz_type='dist_bar',
|
|
||||||
datasource_type='table',
|
|
||||||
table=tbl,
|
|
||||||
params=get_slice_json(
|
|
||||||
slice_name, flt_eq_1="other", viz_type="dist_bar",
|
|
||||||
metrics=['sum__sum_girls', 'sum__sum_boys'],
|
|
||||||
groupby=['state'], flt_op_1='not in', flt_col_1='state'))
|
|
||||||
session.add(slc)
|
|
||||||
slices.append(slc)
|
|
||||||
|
|
||||||
slice_name = "Trends"
|
|
||||||
slc = session.query(Slice).filter_by(slice_name=slice_name).first()
|
|
||||||
if not slc:
|
|
||||||
slc = Slice(
|
|
||||||
slice_name=slice_name,
|
|
||||||
viz_type='line',
|
|
||||||
datasource_type='table',
|
|
||||||
table=tbl,
|
|
||||||
params=get_slice_json(
|
|
||||||
slice_name, viz_type="line", groupby=['name'],
|
|
||||||
granularity='ds', rich_tooltip='y', show_legend='y'))
|
|
||||||
session.add(slc)
|
|
||||||
slices.append(slc)
|
|
||||||
|
|
||||||
slice_name = "Title"
|
|
||||||
slc = session.query(Slice).filter_by(slice_name=slice_name).first()
|
|
||||||
code = """
|
|
||||||
### Birth Names Dashboard
|
|
||||||
The source dataset came from [here](https://github.com/hadley/babynames)
|
|
||||||
|
|
||||||
![img](http://monblog.system-linux.net/image/tux/baby-tux_overlord59-tux.png)
|
|
||||||
"""
|
|
||||||
if not slc:
|
|
||||||
slc = Slice(
|
|
||||||
slice_name=slice_name,
|
|
||||||
viz_type='markup',
|
|
||||||
datasource_type='table',
|
|
||||||
table=tbl,
|
|
||||||
params=get_slice_json(
|
|
||||||
slice_name, viz_type="markup", markup_type="markdown",
|
|
||||||
code=code))
|
|
||||||
session.add(slc)
|
|
||||||
slices.append(slc)
|
|
||||||
|
|
||||||
slice_name = "Name Cloud"
|
|
||||||
slc = session.query(Slice).filter_by(slice_name=slice_name).first()
|
|
||||||
if not slc:
|
|
||||||
slc = Slice(
|
|
||||||
slice_name=slice_name,
|
|
||||||
viz_type='word_cloud',
|
|
||||||
datasource_type='table',
|
|
||||||
table=tbl,
|
|
||||||
params=get_slice_json(
|
|
||||||
slice_name, viz_type="word_cloud", size_from="10",
|
|
||||||
groupby=['name'], size_to="70", rotation="square",
|
|
||||||
limit='100'))
|
|
||||||
session.add(slc)
|
|
||||||
slices.append(slc)
|
|
||||||
|
|
||||||
slice_name = "Pivot Table"
|
|
||||||
slc = session.query(Slice).filter_by(slice_name=slice_name).first()
|
|
||||||
if not slc:
|
|
||||||
slc = Slice(
|
|
||||||
slice_name=slice_name,
|
|
||||||
viz_type='pivot_table',
|
|
||||||
datasource_type='table',
|
|
||||||
table=tbl,
|
|
||||||
params=get_slice_json(
|
|
||||||
slice_name, viz_type="pivot_table", metrics=['sum__num'],
|
|
||||||
groupby=['name'], columns=['state']))
|
|
||||||
session.add(slc)
|
|
||||||
slices.append(slc)
|
|
||||||
|
|
||||||
print("Creating a dashboard")
|
|
||||||
Dash = models.Dashboard
|
|
||||||
dash = session.query(Dash).filter_by(dashboard_title="Births").first()
|
|
||||||
if not dash:
|
|
||||||
dash = Dash(
|
|
||||||
dashboard_title="Births",
|
|
||||||
position_json="""
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"size_y": 4,
|
|
||||||
"size_x": 2,
|
|
||||||
"col": 3,
|
|
||||||
"slice_id": "1",
|
|
||||||
"row": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size_y": 4,
|
|
||||||
"size_x": 2,
|
|
||||||
"col": 1,
|
|
||||||
"slice_id": "2",
|
|
||||||
"row": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size_y": 2,
|
|
||||||
"size_x": 2,
|
|
||||||
"col": 1,
|
|
||||||
"slice_id": "3",
|
|
||||||
"row": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size_y": 2,
|
|
||||||
"size_x": 2,
|
|
||||||
"col": 3,
|
|
||||||
"slice_id": "4",
|
|
||||||
"row": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size_y": 3,
|
|
||||||
"size_x": 7,
|
|
||||||
"col": 5,
|
|
||||||
"slice_id": "5",
|
|
||||||
"row": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size_y": 5,
|
|
||||||
"size_x": 11,
|
|
||||||
"col": 1,
|
|
||||||
"slice_id": "6",
|
|
||||||
"row": 7
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size_y": 3,
|
|
||||||
"size_x": 3,
|
|
||||||
"col": 9,
|
|
||||||
"slice_id": "7",
|
|
||||||
"row": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size_y": 3,
|
|
||||||
"size_x": 4,
|
|
||||||
"col": 5,
|
|
||||||
"slice_id": "8",
|
|
||||||
"row": 1
|
|
||||||
}
|
|
||||||
]
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
session.add(dash)
|
|
||||||
for s in slices:
|
|
||||||
dash.slices.append(s)
|
|
||||||
session.commit()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
@ -0,0 +1,351 @@
|
||||||
|
from datetime import datetime
|
||||||
|
import csv
|
||||||
|
import gzip
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
from flask.ext.appbuilder import Base
|
||||||
|
import pandas as pd
|
||||||
|
from sqlalchemy import Column, String, DateTime, Table, Integer
|
||||||
|
|
||||||
|
from panoramix import app, db, models
|
||||||
|
|
||||||
|
config = app.config
|
||||||
|
|
||||||
|
DATA_FOLDER = os.path.join(config.get("BASE_DIR"), 'data')
|
||||||
|
|
||||||
|
|
||||||
|
def get_or_create_db(session):
|
||||||
|
print("Creating database reference")
|
||||||
|
DB = models.Database
|
||||||
|
dbobj = session.query(DB).filter_by(database_name='main').first()
|
||||||
|
if not dbobj:
|
||||||
|
dbobj = DB(database_name="main")
|
||||||
|
print(config.get("SQLALCHEMY_DATABASE_URI"))
|
||||||
|
dbobj.sqlalchemy_uri = config.get("SQLALCHEMY_DATABASE_URI")
|
||||||
|
session.add(dbobj)
|
||||||
|
session.commit()
|
||||||
|
return dbobj
|
||||||
|
|
||||||
|
|
||||||
|
def load_world_bank_health_n_pop():
|
||||||
|
"""
|
||||||
|
Details on how the data was loaded from
|
||||||
|
http://data.worldbank.org/data-catalog/health-nutrition-and-population-statistics
|
||||||
|
DIR = ""
|
||||||
|
df_country = pd.read_csv(DIR + '/HNP_Country.csv')
|
||||||
|
df_country.columns = ['country_code'] + list(df_country.columns[1:])
|
||||||
|
df_country = df_country[['country_code', 'Region']]
|
||||||
|
df_country.columns = ['country_code', 'region']
|
||||||
|
|
||||||
|
df = pd.read_csv(DIR + '/HNP_Data.csv')
|
||||||
|
del df['Unnamed: 60']
|
||||||
|
df.columns = ['country_name', 'country_code'] + list(df.columns[2:])
|
||||||
|
ndf = df.merge(df_country, how='inner')
|
||||||
|
|
||||||
|
dims = ('country_name', 'country_code', 'region')
|
||||||
|
vv = [str(i) for i in range(1960, 2015)]
|
||||||
|
mdf = pd.melt(ndf, id_vars=dims + ('Indicator Code',), value_vars=vv)
|
||||||
|
mdf['year'] = mdf.variable + '-01-01'
|
||||||
|
dims = dims + ('year',)
|
||||||
|
|
||||||
|
pdf = mdf.pivot_table(values='value', columns='Indicator Code', index=dims)
|
||||||
|
pdf = pdf.reset_index()
|
||||||
|
pdf.to_csv(DIR + '/countries.csv')
|
||||||
|
pdf.to_json(DIR + '/countries.json', orient='records')
|
||||||
|
"""
|
||||||
|
tbl = 'wb_health_population'
|
||||||
|
with gzip.open(os.path.join(DATA_FOLDER, 'countries.json.gz')) as f:
|
||||||
|
pdf = pd.read_json(f)
|
||||||
|
pdf.year = pd.to_datetime(pdf.year)
|
||||||
|
pdf.to_sql(
|
||||||
|
tbl,
|
||||||
|
db.engine,
|
||||||
|
if_exists='replace',
|
||||||
|
chunksize=500,
|
||||||
|
dtype={
|
||||||
|
'year': DateTime(),
|
||||||
|
'country_code': String(3),
|
||||||
|
'country_name': String(255),
|
||||||
|
'region': String(255),
|
||||||
|
},
|
||||||
|
index=False)
|
||||||
|
print("Creating table reference")
|
||||||
|
TBL = models.SqlaTable
|
||||||
|
obj = db.session.query(TBL).filter_by(table_name=tbl).first()
|
||||||
|
if not obj:
|
||||||
|
obj = TBL(table_name='wb_health_population')
|
||||||
|
obj.main_dttm_col = 'year'
|
||||||
|
obj.database = get_or_create_db(db.session)
|
||||||
|
models.Table
|
||||||
|
db.session.add(obj)
|
||||||
|
db.session.commit()
|
||||||
|
obj.fetch_metadata()
|
||||||
|
|
||||||
|
|
||||||
|
def load_birth_names():
|
||||||
|
session = db.session
|
||||||
|
with gzip.open(os.path.join(DATA_FOLDER, 'birth_names.json.gz')) as f:
|
||||||
|
pdf = pd.read_json(f)
|
||||||
|
pdf.ds = pd.to_datetime(pdf.ds, unit='ms')
|
||||||
|
pdf.to_sql(
|
||||||
|
'birth_names',
|
||||||
|
db.engine,
|
||||||
|
if_exists='replace',
|
||||||
|
chunksize=500,
|
||||||
|
dtype={
|
||||||
|
'ds': DateTime,
|
||||||
|
'gender': String(16),
|
||||||
|
'state': String(10),
|
||||||
|
'name': String(255),
|
||||||
|
},
|
||||||
|
index=False)
|
||||||
|
l = []
|
||||||
|
print("Done loading table!")
|
||||||
|
print("-" * 80)
|
||||||
|
|
||||||
|
print("Creating table reference")
|
||||||
|
TBL = models.SqlaTable
|
||||||
|
obj = db.session.query(TBL).filter_by(table_name='birth_names').first()
|
||||||
|
if not obj:
|
||||||
|
obj = TBL(table_name = 'birth_names')
|
||||||
|
obj.main_dttm_col = 'ds'
|
||||||
|
obj.database = get_or_create_db(db.session)
|
||||||
|
models.Table
|
||||||
|
db.session.add(obj)
|
||||||
|
db.session.commit()
|
||||||
|
obj.fetch_metadata()
|
||||||
|
tbl = obj
|
||||||
|
|
||||||
|
print("Creating some slices")
|
||||||
|
def get_slice_json(slice_name, **kwargs):
|
||||||
|
defaults = {
|
||||||
|
"compare_lag": "10",
|
||||||
|
"compare_suffix": "o10Y",
|
||||||
|
"datasource_id": "1",
|
||||||
|
"datasource_name": "birth_names",
|
||||||
|
"datasource_type": "table",
|
||||||
|
"limit": "25",
|
||||||
|
"flt_col_1": "gender",
|
||||||
|
"flt_eq_1": "",
|
||||||
|
"flt_op_1": "in",
|
||||||
|
"granularity": "ds",
|
||||||
|
"groupby": [],
|
||||||
|
"metric": 'sum__num',
|
||||||
|
"metrics": ["sum__num"],
|
||||||
|
"row_limit": config.get("ROW_LIMIT"),
|
||||||
|
"since": "100 years",
|
||||||
|
"slice_name": slice_name,
|
||||||
|
"until": "now",
|
||||||
|
"viz_type": "table",
|
||||||
|
"where": "",
|
||||||
|
"markup_type": "markdown",
|
||||||
|
}
|
||||||
|
d = defaults.copy()
|
||||||
|
d.update(kwargs)
|
||||||
|
return json.dumps(d, indent=4, sort_keys=True)
|
||||||
|
Slice = models.Slice
|
||||||
|
slices = []
|
||||||
|
|
||||||
|
slice_name = "Girls"
|
||||||
|
slc = db.session.query(Slice).filter_by(slice_name=slice_name).first()
|
||||||
|
if not slc:
|
||||||
|
slc = Slice(
|
||||||
|
slice_name=slice_name,
|
||||||
|
viz_type='table',
|
||||||
|
datasource_type='table',
|
||||||
|
table=tbl,
|
||||||
|
params=get_slice_json(
|
||||||
|
slice_name, groupby=['name'], flt_eq_1="girl", row_limit=50))
|
||||||
|
session.add(slc)
|
||||||
|
slices.append(slc)
|
||||||
|
|
||||||
|
slice_name = "Boys"
|
||||||
|
slc = session.query(Slice).filter_by(slice_name=slice_name).first()
|
||||||
|
if not slc:
|
||||||
|
slc = Slice(
|
||||||
|
slice_name=slice_name,
|
||||||
|
viz_type='table',
|
||||||
|
datasource_type='table',
|
||||||
|
table=tbl,
|
||||||
|
params=get_slice_json(
|
||||||
|
slice_name, groupby=['name'], flt_eq_1="boy", row_limit=50))
|
||||||
|
session.add(slc)
|
||||||
|
slices.append(slc)
|
||||||
|
|
||||||
|
slice_name = "Participants"
|
||||||
|
slc = session.query(Slice).filter_by(slice_name=slice_name).first()
|
||||||
|
if not slc:
|
||||||
|
slc = Slice(
|
||||||
|
slice_name=slice_name,
|
||||||
|
viz_type='big_number',
|
||||||
|
datasource_type='table',
|
||||||
|
table=tbl,
|
||||||
|
params=get_slice_json(
|
||||||
|
slice_name, viz_type="big_number", granularity="ds",
|
||||||
|
compare_lag="5", compare_suffix="over 5Y"))
|
||||||
|
session.add(slc)
|
||||||
|
slices.append(slc)
|
||||||
|
|
||||||
|
slice_name = "Genders"
|
||||||
|
slc = session.query(Slice).filter_by(slice_name=slice_name).first()
|
||||||
|
if not slc:
|
||||||
|
slc = Slice(
|
||||||
|
slice_name=slice_name,
|
||||||
|
viz_type='pie',
|
||||||
|
datasource_type='table',
|
||||||
|
table=tbl,
|
||||||
|
params=get_slice_json(
|
||||||
|
slice_name, viz_type="pie", groupby=['gender']))
|
||||||
|
session.add(slc)
|
||||||
|
slices.append(slc)
|
||||||
|
|
||||||
|
slice_name = "Gender by State"
|
||||||
|
slc = session.query(Slice).filter_by(slice_name=slice_name).first()
|
||||||
|
if not slc:
|
||||||
|
slc = Slice(
|
||||||
|
slice_name=slice_name,
|
||||||
|
viz_type='dist_bar',
|
||||||
|
datasource_type='table',
|
||||||
|
table=tbl,
|
||||||
|
params=get_slice_json(
|
||||||
|
slice_name, flt_eq_1="other", viz_type="dist_bar",
|
||||||
|
metrics=['sum__sum_girls', 'sum__sum_boys'],
|
||||||
|
groupby=['state'], flt_op_1='not in', flt_col_1='state'))
|
||||||
|
session.add(slc)
|
||||||
|
slices.append(slc)
|
||||||
|
|
||||||
|
slice_name = "Trends"
|
||||||
|
slc = session.query(Slice).filter_by(slice_name=slice_name).first()
|
||||||
|
if not slc:
|
||||||
|
slc = Slice(
|
||||||
|
slice_name=slice_name,
|
||||||
|
viz_type='line',
|
||||||
|
datasource_type='table',
|
||||||
|
table=tbl,
|
||||||
|
params=get_slice_json(
|
||||||
|
slice_name, viz_type="line", groupby=['name'],
|
||||||
|
granularity='ds', rich_tooltip='y', show_legend='y'))
|
||||||
|
session.add(slc)
|
||||||
|
slices.append(slc)
|
||||||
|
|
||||||
|
slice_name = "Title"
|
||||||
|
slc = session.query(Slice).filter_by(slice_name=slice_name).first()
|
||||||
|
code = """
|
||||||
|
### Birth Names Dashboard
|
||||||
|
The source dataset came from [here](https://github.com/hadley/babynames)
|
||||||
|
|
||||||
|
![img](http://monblog.system-linux.net/image/tux/baby-tux_overlord59-tux.png)
|
||||||
|
"""
|
||||||
|
if not slc:
|
||||||
|
slc = Slice(
|
||||||
|
slice_name=slice_name,
|
||||||
|
viz_type='markup',
|
||||||
|
datasource_type='table',
|
||||||
|
table=tbl,
|
||||||
|
params=get_slice_json(
|
||||||
|
slice_name, viz_type="markup", markup_type="markdown",
|
||||||
|
code=code))
|
||||||
|
session.add(slc)
|
||||||
|
slices.append(slc)
|
||||||
|
|
||||||
|
slice_name = "Name Cloud"
|
||||||
|
slc = session.query(Slice).filter_by(slice_name=slice_name).first()
|
||||||
|
if not slc:
|
||||||
|
slc = Slice(
|
||||||
|
slice_name=slice_name,
|
||||||
|
viz_type='word_cloud',
|
||||||
|
datasource_type='table',
|
||||||
|
table=tbl,
|
||||||
|
params=get_slice_json(
|
||||||
|
slice_name, viz_type="word_cloud", size_from="10",
|
||||||
|
groupby=['name'], size_to="70", rotation="square",
|
||||||
|
limit='100'))
|
||||||
|
session.add(slc)
|
||||||
|
slices.append(slc)
|
||||||
|
|
||||||
|
slice_name = "Pivot Table"
|
||||||
|
slc = session.query(Slice).filter_by(slice_name=slice_name).first()
|
||||||
|
if not slc:
|
||||||
|
slc = Slice(
|
||||||
|
slice_name=slice_name,
|
||||||
|
viz_type='pivot_table',
|
||||||
|
datasource_type='table',
|
||||||
|
table=tbl,
|
||||||
|
params=get_slice_json(
|
||||||
|
slice_name, viz_type="pivot_table", metrics=['sum__num'],
|
||||||
|
groupby=['name'], columns=['state']))
|
||||||
|
session.add(slc)
|
||||||
|
slices.append(slc)
|
||||||
|
|
||||||
|
print("Creating a dashboard")
|
||||||
|
Dash = models.Dashboard
|
||||||
|
dash = session.query(Dash).filter_by(dashboard_title="Births").first()
|
||||||
|
if not dash:
|
||||||
|
dash = Dash(
|
||||||
|
dashboard_title="Births",
|
||||||
|
position_json="""
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"size_y": 4,
|
||||||
|
"size_x": 2,
|
||||||
|
"col": 3,
|
||||||
|
"slice_id": "1",
|
||||||
|
"row": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size_y": 4,
|
||||||
|
"size_x": 2,
|
||||||
|
"col": 1,
|
||||||
|
"slice_id": "2",
|
||||||
|
"row": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size_y": 2,
|
||||||
|
"size_x": 2,
|
||||||
|
"col": 1,
|
||||||
|
"slice_id": "3",
|
||||||
|
"row": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size_y": 2,
|
||||||
|
"size_x": 2,
|
||||||
|
"col": 3,
|
||||||
|
"slice_id": "4",
|
||||||
|
"row": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size_y": 3,
|
||||||
|
"size_x": 7,
|
||||||
|
"col": 5,
|
||||||
|
"slice_id": "5",
|
||||||
|
"row": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size_y": 5,
|
||||||
|
"size_x": 11,
|
||||||
|
"col": 1,
|
||||||
|
"slice_id": "6",
|
||||||
|
"row": 7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size_y": 3,
|
||||||
|
"size_x": 3,
|
||||||
|
"col": 9,
|
||||||
|
"slice_id": "7",
|
||||||
|
"row": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size_y": 3,
|
||||||
|
"size_x": 4,
|
||||||
|
"col": 5,
|
||||||
|
"slice_id": "8",
|
||||||
|
"row": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
session.add(dash)
|
||||||
|
for s in slices:
|
||||||
|
dash.slices.append(s)
|
||||||
|
session.commit()
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
@ -32,6 +32,7 @@ class SelectMultipleSortableField(SelectMultipleField):
|
||||||
d[value] = (value, label, selected)
|
d[value] = (value, label, selected)
|
||||||
if self.data:
|
if self.data:
|
||||||
for value in self.data:
|
for value in self.data:
|
||||||
|
if value:
|
||||||
yield d.pop(value)
|
yield d.pop(value)
|
||||||
while d:
|
while d:
|
||||||
yield d.pop(d.keys()[0])
|
yield d.pop(d.keys()[0])
|
||||||
|
@ -402,6 +403,7 @@ class FormFactory(object):
|
||||||
css_classes = field_css_classes
|
css_classes = field_css_classes
|
||||||
standalone = HiddenField()
|
standalone = HiddenField()
|
||||||
async = HiddenField()
|
async = HiddenField()
|
||||||
|
extra_filters = HiddenField()
|
||||||
json = HiddenField()
|
json = HiddenField()
|
||||||
slice_id = HiddenField()
|
slice_id = HiddenField()
|
||||||
slice_name = HiddenField()
|
slice_name = HiddenField()
|
||||||
|
|
|
@ -1,8 +1,17 @@
|
||||||
from datetime import timedelta
|
from copy import deepcopy, copy
|
||||||
|
from collections import namedtuple
|
||||||
|
from datetime import timedelta, datetime
|
||||||
|
import json
|
||||||
|
from six import string_types
|
||||||
|
import sqlparse
|
||||||
|
import requests
|
||||||
|
import textwrap
|
||||||
|
|
||||||
from dateutil.parser import parse
|
from dateutil.parser import parse
|
||||||
from flask import flash
|
from flask import flash
|
||||||
from flask.ext.appbuilder import Model
|
from flask.ext.appbuilder import Model
|
||||||
from flask.ext.appbuilder.models.mixins import AuditMixin
|
from flask.ext.appbuilder.models.mixins import AuditMixin
|
||||||
|
import pandas as pd
|
||||||
from pandas import read_sql_query
|
from pandas import read_sql_query
|
||||||
from pydruid import client
|
from pydruid import client
|
||||||
from pydruid.utils.filters import Dimension, Filter
|
from pydruid.utils.filters import Dimension, Filter
|
||||||
|
@ -11,19 +20,11 @@ from sqlalchemy import (
|
||||||
Column, Integer, String, ForeignKey, Text, Boolean, DateTime,
|
Column, Integer, String, ForeignKey, Text, Boolean, DateTime,
|
||||||
Table, create_engine, MetaData, desc, select, and_, func)
|
Table, create_engine, MetaData, desc, select, and_, func)
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
from sqlalchemy.sql import table, literal_column, text
|
from sqlalchemy.sql import table, literal_column, text, column
|
||||||
from sqlalchemy.sql.elements import ColumnClause
|
from sqlalchemy.sql.elements import ColumnClause
|
||||||
from sqlalchemy_utils import EncryptedType
|
from sqlalchemy_utils import EncryptedType
|
||||||
|
|
||||||
from copy import deepcopy, copy
|
|
||||||
from collections import namedtuple
|
|
||||||
from datetime import datetime
|
|
||||||
import json
|
|
||||||
import sqlparse
|
|
||||||
import requests
|
|
||||||
import textwrap
|
|
||||||
|
|
||||||
from six import string_types
|
|
||||||
|
|
||||||
from panoramix import app, db, get_session, utils
|
from panoramix import app, db, get_session, utils
|
||||||
from panoramix.viz import viz_types
|
from panoramix.viz import viz_types
|
||||||
|
@ -85,6 +86,16 @@ class Slice(Model, AuditMixinNullable):
|
||||||
def datasource_id(self):
|
def datasource_id(self):
|
||||||
return self.table_id or self.druid_datasource_id
|
return self.table_id or self.druid_datasource_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def data(self):
|
||||||
|
d = self.viz.data
|
||||||
|
d['slice_id'] = self.id
|
||||||
|
return d
|
||||||
|
|
||||||
|
@property
|
||||||
|
def json_data(self):
|
||||||
|
return json.dumps(self.data)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def slice_url(self):
|
def slice_url(self):
|
||||||
try:
|
try:
|
||||||
|
@ -409,9 +420,7 @@ class SqlaTable(Model, Queryable, AuditMixinNullable):
|
||||||
groupby_exprs = []
|
groupby_exprs = []
|
||||||
|
|
||||||
if groupby:
|
if groupby:
|
||||||
select_exprs = [literal_column(s) for s in groupby]
|
|
||||||
select_exprs = []
|
select_exprs = []
|
||||||
groupby_exprs = []
|
|
||||||
inner_select_exprs = []
|
inner_select_exprs = []
|
||||||
inner_groupby_exprs = []
|
inner_groupby_exprs = []
|
||||||
for s in groupby:
|
for s in groupby:
|
||||||
|
@ -421,8 +430,8 @@ class SqlaTable(Model, Queryable, AuditMixinNullable):
|
||||||
outer = ColumnClause(expr, is_literal=True).label(s)
|
outer = ColumnClause(expr, is_literal=True).label(s)
|
||||||
inner = ColumnClause(expr, is_literal=True).label('__' + s)
|
inner = ColumnClause(expr, is_literal=True).label('__' + s)
|
||||||
else:
|
else:
|
||||||
outer = literal_column(s).label(s)
|
outer = column(s).label(s)
|
||||||
inner = literal_column(s).label('__' + s)
|
inner = column(s).label('__' + s)
|
||||||
|
|
||||||
groupby_exprs.append(outer)
|
groupby_exprs.append(outer)
|
||||||
select_exprs.append(outer)
|
select_exprs.append(outer)
|
||||||
|
@ -462,7 +471,7 @@ class SqlaTable(Model, Queryable, AuditMixinNullable):
|
||||||
cond = ColumnClause(
|
cond = ColumnClause(
|
||||||
col_obj.expression, is_literal=True).in_(values)
|
col_obj.expression, is_literal=True).in_(values)
|
||||||
else:
|
else:
|
||||||
cond = literal_column(col).in_(values)
|
cond = column(col).in_(values)
|
||||||
if op == 'not in':
|
if op == 'not in':
|
||||||
cond = ~cond
|
cond = ~cond
|
||||||
where_clause_and.append(cond)
|
where_clause_and.append(cond)
|
||||||
|
@ -486,7 +495,7 @@ class SqlaTable(Model, Queryable, AuditMixinNullable):
|
||||||
on_clause = []
|
on_clause = []
|
||||||
for i, gb in enumerate(groupby):
|
for i, gb in enumerate(groupby):
|
||||||
on_clause.append(
|
on_clause.append(
|
||||||
groupby_exprs[i] == literal_column("__" + gb))
|
groupby_exprs[i] == column("__" + gb))
|
||||||
|
|
||||||
from_clause = from_clause.join(subq.alias(), and_(*on_clause))
|
from_clause = from_clause.join(subq.alias(), and_(*on_clause))
|
||||||
|
|
||||||
|
@ -499,7 +508,6 @@ class SqlaTable(Model, Queryable, AuditMixinNullable):
|
||||||
con=engine
|
con=engine
|
||||||
)
|
)
|
||||||
sql = sqlparse.format(sql, reindent=True)
|
sql = sqlparse.format(sql, reindent=True)
|
||||||
print(sql)
|
|
||||||
return QueryResult(
|
return QueryResult(
|
||||||
df=df, duration=datetime.now() - qry_start_dttm, query=sql)
|
df=df, duration=datetime.now() - qry_start_dttm, query=sql)
|
||||||
|
|
||||||
|
@ -547,33 +555,35 @@ class SqlaTable(Model, Queryable, AuditMixinNullable):
|
||||||
if not any_date_col and 'date' in datatype.lower():
|
if not any_date_col and 'date' in datatype.lower():
|
||||||
any_date_col = col.name
|
any_date_col = col.name
|
||||||
|
|
||||||
|
quoted = "{}".format(
|
||||||
|
column(dbcol.column_name).compile(dialect=db.engine.dialect))
|
||||||
if dbcol.sum:
|
if dbcol.sum:
|
||||||
metrics.append(M(
|
metrics.append(M(
|
||||||
metric_name='sum__' + dbcol.column_name,
|
metric_name='sum__' + dbcol.column_name,
|
||||||
verbose_name='sum__' + dbcol.column_name,
|
verbose_name='sum__' + dbcol.column_name,
|
||||||
metric_type='sum',
|
metric_type='sum',
|
||||||
expression="SUM({})".format(dbcol.column_name)
|
expression="SUM({})".format(quoted)
|
||||||
))
|
))
|
||||||
if dbcol.max:
|
if dbcol.max:
|
||||||
metrics.append(M(
|
metrics.append(M(
|
||||||
metric_name='max__' + dbcol.column_name,
|
metric_name='max__' + dbcol.column_name,
|
||||||
verbose_name='max__' + dbcol.column_name,
|
verbose_name='max__' + dbcol.column_name,
|
||||||
metric_type='max',
|
metric_type='max',
|
||||||
expression="MAX({})".format(dbcol.column_name)
|
expression="MAX({})".format(quoted)
|
||||||
))
|
))
|
||||||
if dbcol.min:
|
if dbcol.min:
|
||||||
metrics.append(M(
|
metrics.append(M(
|
||||||
metric_name='min__' + dbcol.column_name,
|
metric_name='min__' + dbcol.column_name,
|
||||||
verbose_name='min__' + dbcol.column_name,
|
verbose_name='min__' + dbcol.column_name,
|
||||||
metric_type='min',
|
metric_type='min',
|
||||||
expression="MIN({})".format(dbcol.column_name)
|
expression="MIN({})".format(quoted)
|
||||||
))
|
))
|
||||||
if dbcol.count_distinct:
|
if dbcol.count_distinct:
|
||||||
metrics.append(M(
|
metrics.append(M(
|
||||||
metric_name='count_distinct__' + dbcol.column_name,
|
metric_name='count_distinct__' + dbcol.column_name,
|
||||||
verbose_name='count_distinct__' + dbcol.column_name,
|
verbose_name='count_distinct__' + dbcol.column_name,
|
||||||
metric_type='count_distinct',
|
metric_type='count_distinct',
|
||||||
expression="COUNT(DISTINCT {})".format(dbcol.column_name)
|
expression="COUNT(DISTINCT {})".format(quoted)
|
||||||
))
|
))
|
||||||
dbcol.type = datatype
|
dbcol.type = datatype
|
||||||
db.session.merge(self)
|
db.session.merge(self)
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
html>body{
|
html>body{
|
||||||
margin: 0px; !important
|
margin: 0px; !important
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.slice_container {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
.container-fluid {
|
.container-fluid {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
@ -132,8 +136,8 @@ legend {
|
||||||
.datasource .tooltip-inner {
|
.datasource .tooltip-inner {
|
||||||
max-width: 350px;
|
max-width: 350px;
|
||||||
}
|
}
|
||||||
.datasource img.loading {
|
img.loading {
|
||||||
width: 30px;
|
width: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard a i {
|
.dashboard a i {
|
||||||
|
@ -181,7 +185,7 @@ legend {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
.dashboard table.widget_header {
|
.dashboard table.slice_header {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
}
|
}
|
||||||
|
@ -192,7 +196,7 @@ legend {
|
||||||
.dashboard li.widget.pie,
|
.dashboard li.widget.pie,
|
||||||
.dashboard li.widget.dist_bar,
|
.dashboard li.widget.dist_bar,
|
||||||
.dashboard li.widget.sunburst {
|
.dashboard li.widget.sunburst {
|
||||||
overflow: visible; /* This allows elements within these slice typesin a dashboard to overflow */
|
overflow: visible; /* This allows elements within these widget typesin a dashboard to overflow */
|
||||||
}
|
}
|
||||||
.dashboard div.nvtooltip {
|
.dashboard div.nvtooltip {
|
||||||
z-index: 888; /* this lets tool tips go on top of other slices */
|
z-index: 888; /* this lets tool tips go on top of other slices */
|
||||||
|
|
|
@ -1,63 +1,145 @@
|
||||||
var timer;
|
|
||||||
var px = (function() {
|
var px = (function() {
|
||||||
|
|
||||||
var visualizations = [];
|
var visualizations = {};
|
||||||
|
var dashboard = undefined;
|
||||||
|
|
||||||
function registerWidget(name, initializer) {
|
var Slice = function(data, dashboard){
|
||||||
visualizations[name] = initializer;
|
var timer;
|
||||||
}
|
|
||||||
|
|
||||||
function makeNullWidget() {
|
|
||||||
return {
|
|
||||||
render: function() {},
|
|
||||||
resize: function() {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function initializeWidget(data) {
|
|
||||||
var token = $('#' + data.token);
|
var token = $('#' + data.token);
|
||||||
|
var container_id = data.token + '_con';
|
||||||
|
var selector = '#' + container_id;
|
||||||
|
var container = $(selector);
|
||||||
|
var slice_id = data.slice_id;
|
||||||
var name = data['viz_name'];
|
var name = data['viz_name'];
|
||||||
var initializer = visualizations[name];
|
|
||||||
var user_defined_widget = initializer ? initializer(data) : makeNullWidget();
|
|
||||||
var dttm = 0;
|
var dttm = 0;
|
||||||
var timer;
|
var timer;
|
||||||
var stopwatch = function () {
|
var stopwatch = function () {
|
||||||
dttm += 10;
|
dttm += 10;
|
||||||
$('#timer').text(Math.round(dttm/10)/100 + " sec");
|
$('#timer').text(Math.round(dttm/10)/100 + " sec");
|
||||||
}
|
}
|
||||||
var controler = {
|
var qrystr = '';
|
||||||
|
slice = {
|
||||||
|
jsonEndpoint: function() {
|
||||||
|
var parser = document.createElement('a');
|
||||||
|
parser.href = data.json_endpoint;
|
||||||
|
// Shallow copy
|
||||||
|
if (dashboard !== undefined){
|
||||||
|
qrystr = parser.search + "&extra_filters=" + JSON.stringify(dashboard.filters);
|
||||||
|
}
|
||||||
|
else if ($('#query').length == 0){
|
||||||
|
qrystr = parser.search;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
qrystr = '?' + $('#query').serialize();
|
||||||
|
}
|
||||||
|
var endpoint = parser.pathname + qrystr + "&json=true";
|
||||||
|
return endpoint;
|
||||||
|
},
|
||||||
done: function (data) {
|
done: function (data) {
|
||||||
clearInterval(timer);
|
clearInterval(timer);
|
||||||
token.find("img.loading").hide();
|
token.find("img.loading").hide()
|
||||||
|
container.show();
|
||||||
if(data !== undefined)
|
if(data !== undefined)
|
||||||
$("#query_container").html(data.query);
|
$("#query_container").html(data.query);
|
||||||
$('#timer').removeClass('btn-warning');
|
$('#timer').removeClass('btn-warning');
|
||||||
$('span.query').removeClass('disabled');
|
|
||||||
$('#timer').addClass('btn-success');
|
$('#timer').addClass('btn-success');
|
||||||
|
$('span.query').removeClass('disabled');
|
||||||
},
|
},
|
||||||
error: function (msg) {
|
error: function (msg) {
|
||||||
clearInterval(timer);
|
clearInterval(timer);
|
||||||
token.find("img.loading").hide();
|
token.find("img.loading").hide();
|
||||||
var err = '<div class="alert alert-danger">' + msg + '</div>';
|
var err = '<div class="alert alert-danger">' + msg + '</div>';
|
||||||
token.html(err);
|
container.html(err);
|
||||||
|
container.show();
|
||||||
$('#timer').removeClass('btn-warning');
|
$('#timer').removeClass('btn-warning');
|
||||||
$('span.query').removeClass('disabled');
|
$('span.query').removeClass('disabled');
|
||||||
$('#timer').addClass('btn-danger');
|
$('#timer').addClass('btn-danger');
|
||||||
}
|
},
|
||||||
};
|
data: data,
|
||||||
widget = {
|
container: container,
|
||||||
|
container_id: container_id,
|
||||||
|
selector: selector,
|
||||||
render: function() {
|
render: function() {
|
||||||
|
token.find("img.loading").show();
|
||||||
|
container.hide();
|
||||||
|
container.html('');
|
||||||
|
dttm = 0;
|
||||||
timer = setInterval(stopwatch, 10);
|
timer = setInterval(stopwatch, 10);
|
||||||
user_defined_widget.render(controler);
|
$('#timer').removeClass('btn-danger btn-success');
|
||||||
|
$('#timer').addClass('btn-warning');
|
||||||
|
viz.render();
|
||||||
|
console.log(slice);
|
||||||
|
$('#json').click(function(){window.location=slice.jsonEndpoint()});
|
||||||
|
$('#standalone').click(function(){window.location=slice.data.standalone_endpoint});
|
||||||
|
$('#csv').click(function(){window.location=slice.data.csv_endpoint});
|
||||||
},
|
},
|
||||||
resize: function() {
|
resize: function() {
|
||||||
user_defined_widget.resize();
|
token.find("img.loading").show();
|
||||||
|
container.hide();
|
||||||
|
container.html('');
|
||||||
|
viz.render();
|
||||||
|
viz.resize();
|
||||||
|
},
|
||||||
|
addFilter: function(col, vals) {
|
||||||
|
if(dashboard !== undefined)
|
||||||
|
dashboard.addFilter(slice_id, col, vals);
|
||||||
|
},
|
||||||
|
clearFilter: function() {
|
||||||
|
if(dashboard !== undefined)
|
||||||
|
delete dashboard.clearFilter(slice_id);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
return widget;
|
var viz = visualizations[data.form_data.viz_type](slice);
|
||||||
|
slice['viz'] = viz;
|
||||||
|
return slice;
|
||||||
}
|
}
|
||||||
|
|
||||||
function initializeDatasourceView() {
|
var Dashboard = function(id){
|
||||||
|
var dash = {
|
||||||
|
slices: [],
|
||||||
|
filters: {},
|
||||||
|
id: id,
|
||||||
|
addFilter: function(slice_id, field, values) {
|
||||||
|
this.filters[slice_id] = [field, values];
|
||||||
|
this.refreshExcept(slice_id);
|
||||||
|
},
|
||||||
|
refreshExcept: function(slice_id) {
|
||||||
|
this.slices.forEach(function(slice){
|
||||||
|
if(slice.data.slice_id != slice_id){
|
||||||
|
slice.render();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
clearFilter: function(slice_id) {
|
||||||
|
delete this.filters[slice_id];
|
||||||
|
this.refreshExcept(slice_id);
|
||||||
|
},
|
||||||
|
getSlice: function(slice_id) {
|
||||||
|
for(var i=0; i<this.slices.length; i++){
|
||||||
|
if (this.slices[i].data.slice_id == slice_id)
|
||||||
|
return this.slices[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$('.dashboard li.widget').each(function() {
|
||||||
|
var data = $(this).data('slice');
|
||||||
|
var slice = Slice(data, dash);
|
||||||
|
$(this).find('a.refresh').click(function(){
|
||||||
|
slice.render();
|
||||||
|
});
|
||||||
|
dash.slices.push(slice);
|
||||||
|
slice.render();
|
||||||
|
});
|
||||||
|
dashboard = dash;
|
||||||
|
return dash;
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerViz(name, initViz) {
|
||||||
|
|
||||||
|
visualizations[name] = initViz;
|
||||||
|
}
|
||||||
|
|
||||||
|
function initExploreView() {
|
||||||
function getParam(name) {
|
function getParam(name) {
|
||||||
name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
|
name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
|
||||||
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
|
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
|
||||||
|
@ -95,8 +177,7 @@ var px = (function() {
|
||||||
$(this).parent().parent().remove();
|
$(this).parent().parent().remove();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
function prepForm(){
|
||||||
function druidify(){
|
|
||||||
var i = 1;
|
var i = 1;
|
||||||
// Assigning the right id to form elements in filters
|
// Assigning the right id to form elements in filters
|
||||||
$("#filters > div").each(function() {
|
$("#filters > div").each(function() {
|
||||||
|
@ -112,7 +193,11 @@ var px = (function() {
|
||||||
.attr("name", function() {return "flt_eq_" + i;});
|
.attr("name", function() {return "flt_eq_" + i;});
|
||||||
i++;
|
i++;
|
||||||
});
|
});
|
||||||
$("#query").submit();
|
}
|
||||||
|
|
||||||
|
function druidify(){
|
||||||
|
prepForm();
|
||||||
|
slice.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
$("#plus").click(add_filter);
|
$("#plus").click(add_filter);
|
||||||
|
@ -120,15 +205,17 @@ var px = (function() {
|
||||||
var slice_name = prompt("Name your slice!");
|
var slice_name = prompt("Name your slice!");
|
||||||
if (slice_name != "" && slice_name != null) {
|
if (slice_name != "" && slice_name != null) {
|
||||||
$("#slice_name").val(slice_name);
|
$("#slice_name").val(slice_name);
|
||||||
|
prepForm();
|
||||||
$("#action").val("save");
|
$("#action").val("save");
|
||||||
druidify();
|
$("#query").submit();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$("#btn_overwrite").click(function () {
|
$("#btn_overwrite").click(function () {
|
||||||
var flag = confirm("Overwrite slice [" + $("#slice_name").val() + "] !?");
|
var flag = confirm("Overwrite slice [" + $("#slice_name").val() + "] !?");
|
||||||
if (flag) {
|
if (flag) {
|
||||||
$("#action").val("overwrite");
|
$("#action").val("overwrite");
|
||||||
druidify();
|
prepForm();
|
||||||
|
$("#query").submit();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
add_filter();
|
add_filter();
|
||||||
|
@ -176,7 +263,7 @@ var px = (function() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function initializeDashboardView(dashboard_id) {
|
function initDashboardView() {
|
||||||
var gridster = $(".gridster ul").gridster({
|
var gridster = $(".gridster ul").gridster({
|
||||||
widget_margins: [5, 5],
|
widget_margins: [5, 5],
|
||||||
widget_base_dimensions: [100, 100],
|
widget_base_dimensions: [100, 100],
|
||||||
|
@ -186,8 +273,8 @@ function initializeDashboardView(dashboard_id) {
|
||||||
resize: {
|
resize: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
stop: function(e, ui, element) {
|
stop: function(e, ui, element) {
|
||||||
var widget = $(element).data('widget');
|
var slice_data = $(element).data('slice');
|
||||||
widget.resize();
|
dashboard.getSlice(slice_data.slice_id).resize();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
serialize_params: function(_w, wgd) {
|
serialize_params: function(_w, wgd) {
|
||||||
|
@ -208,42 +295,45 @@ function initializeDashboardView(dashboard_id) {
|
||||||
};
|
};
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: "POST",
|
type: "POST",
|
||||||
url: '/panoramix/save_dash/' + dashboard_id + '/',
|
url: '/panoramix/save_dash/' + dashboard.id + '/',
|
||||||
data: {'data': JSON.stringify(data)},
|
data: {'data': JSON.stringify(data)},
|
||||||
success: function() {alert("Saved!")},
|
success: function() {alert("Saved!")},
|
||||||
error: function() {alert("Error :(")},
|
error: function() {alert("Error :(")},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
$("a.closewidget").click(function() {
|
$("a.closeslice").click(function() {
|
||||||
var li = $(this).parents("li");
|
var li = $(this).parents("li");
|
||||||
gridster.remove_widget(li);
|
gridster.remove_widget(li);
|
||||||
});
|
});
|
||||||
$("table.widget_header").mouseover(function() {
|
$("table.slice_header").mouseover(function() {
|
||||||
$(this).find("td.icons nobr").show();
|
$(this).find("td.icons nobr").show();
|
||||||
});
|
});
|
||||||
$("table.widget_header").mouseout(function() {
|
$("table.slice_header").mouseout(function() {
|
||||||
$(this).find("td.icons nobr").hide();
|
$(this).find("td.icons nobr").hide();
|
||||||
});
|
});
|
||||||
$("#dash_css").on("keyup", function(){
|
$("#dash_css").on("keyup", function(){
|
||||||
css = $(this).val();
|
css = $(this).val();
|
||||||
$("#user_style").html(css);
|
$("#user_style").html(css);
|
||||||
});
|
});
|
||||||
$('li.widget').each(function() { /* this sets the z-index for left side boxes higher. */
|
|
||||||
|
// this sets the z-index for left side boxes higher
|
||||||
|
$('li.slice').each(function() {
|
||||||
current_row = $(this).attr('data-col');
|
current_row = $(this).attr('data-col');
|
||||||
$( this ).css('z-index', 100 - current_row);
|
$( this ).css('z-index', 100 - current_row);
|
||||||
});
|
});
|
||||||
$("div.chart").each(function() { /* this makes the whole chart fit within the dashboard div */
|
|
||||||
|
// this makes the whole chart fit within the dashboard div
|
||||||
|
$("div.chart").each(function() {
|
||||||
$(this).css('height', '95%');
|
$(this).css('height', '95%');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Export public functions
|
// Export public functions
|
||||||
return {
|
return {
|
||||||
registerWidget: registerWidget,
|
registerViz: registerViz,
|
||||||
initializeWidget: initializeWidget,
|
Slice: Slice,
|
||||||
initializeDatasourceView: initializeDatasourceView,
|
Dashboard: Dashboard,
|
||||||
initializeDashboardView: initializeDashboardView,
|
initExploreView: initExploreView,
|
||||||
|
initDashboardView: initDashboardView,
|
||||||
}
|
}
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,12 @@
|
||||||
px.registerWidget('big_number', function(data_attribute) {
|
px.registerViz('big_number', function(slice) {
|
||||||
|
var data_attribute = slice.data;
|
||||||
|
var div = d3.select(slice.selector);
|
||||||
|
|
||||||
var token_name = data_attribute['token'];
|
function render() {
|
||||||
var json_callback = data_attribute['json_endpoint'];
|
d3.json(slice.jsonEndpoint(), function(error, payload){
|
||||||
var div = d3.select('#' + token_name);
|
|
||||||
|
|
||||||
function render(ctrl) {
|
|
||||||
d3.json(json_callback, function(error, payload){
|
|
||||||
//Define the percentage bounds that define color from red to green
|
//Define the percentage bounds that define color from red to green
|
||||||
div.html("");
|
|
||||||
if (error != null){
|
if (error != null){
|
||||||
ctrl.error(error.responseText);
|
slice.error(error.responseText);
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
json = payload.data;
|
json = payload.data;
|
||||||
|
@ -19,9 +16,8 @@ px.registerWidget('big_number', function(data_attribute) {
|
||||||
|
|
||||||
var f = d3.format('.3s');
|
var f = d3.format('.3s');
|
||||||
var fp = d3.format('+.1%');
|
var fp = d3.format('+.1%');
|
||||||
var xy = div.node().getBoundingClientRect();
|
var width = slice.container.width();
|
||||||
var width = xy.width;
|
var height = slice.container.height() - 30;
|
||||||
var height = xy.height - 30;
|
|
||||||
var svg = div.append('svg');
|
var svg = div.append('svg');
|
||||||
svg.attr("width", width);
|
svg.attr("width", width);
|
||||||
svg.attr("height", height);
|
svg.attr("height", height);
|
||||||
|
@ -135,7 +131,7 @@ px.registerWidget('big_number', function(data_attribute) {
|
||||||
div.select('g.digits').transition().duration(500).attr('opacity', 1);
|
div.select('g.digits').transition().duration(500).attr('opacity', 1);
|
||||||
div.select('g.axis').transition().duration(500).attr('opacity', 0);
|
div.select('g.axis').transition().duration(500).attr('opacity', 0);
|
||||||
});
|
});
|
||||||
ctrl.done(payload);
|
slice.done(payload);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,6 @@
|
||||||
stroke: #000;
|
stroke: #000;
|
||||||
stroke-width: 1.5px;
|
stroke-width: 1.5px;
|
||||||
}
|
}
|
||||||
.directed_force #chart {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.directed_force circle {
|
.directed_force circle {
|
||||||
fill: #ccc;
|
fill: #ccc;
|
||||||
|
|
|
@ -2,25 +2,23 @@
|
||||||
Modified from http://bl.ocks.org/d3noob/5141278
|
Modified from http://bl.ocks.org/d3noob/5141278
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function viz_directed_force(data_attribute) {
|
function viz_directed_force(slice) {
|
||||||
var token = d3.select('#' + data_attribute.token);
|
var width = slice.container.width();
|
||||||
var xy = token.select('#chart').node().getBoundingClientRect();
|
var height = slice.container.height() - 25;
|
||||||
var width = xy.width;
|
var link_length = slice.data.form_data['link_length'];
|
||||||
var height = xy.height - 25;
|
var div = d3.select(slice.selector);
|
||||||
var radius = Math.min(width, height) / 2;
|
|
||||||
var link_length = data_attribute.form_data['link_length'];
|
|
||||||
if (link_length === undefined){
|
if (link_length === undefined){
|
||||||
link_length = 200;
|
link_length = 200;
|
||||||
}
|
}
|
||||||
var charge = data_attribute.form_data['charge'];
|
var charge = slice.data.form_data['charge'];
|
||||||
if (charge === undefined){
|
if (charge === undefined){
|
||||||
charge = -500;
|
charge = -500;
|
||||||
}
|
}
|
||||||
var render = function(ctrl) {
|
var render = function() {
|
||||||
d3.json(data_attribute.json_endpoint, function(error, json) {
|
d3.json(slice.jsonEndpoint(), function(error, json) {
|
||||||
|
|
||||||
if (error != null){
|
if (error != null){
|
||||||
ctrl.error(error.responseText);
|
slice.error(error.responseText);
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
links = json.data;
|
links = json.data;
|
||||||
|
@ -59,7 +57,7 @@ function viz_directed_force(data_attribute) {
|
||||||
.on("tick", tick)
|
.on("tick", tick)
|
||||||
.start();
|
.start();
|
||||||
|
|
||||||
var svg = token.select("#chart").append("svg")
|
var svg = div.append("svg")
|
||||||
.attr("width", width)
|
.attr("width", width)
|
||||||
.attr("height", height);
|
.attr("height", height);
|
||||||
|
|
||||||
|
@ -150,7 +148,7 @@ function viz_directed_force(data_attribute) {
|
||||||
.attr("transform", function(d) {
|
.attr("transform", function(d) {
|
||||||
return "translate(" + d.x + "," + d.y + ")"; });
|
return "translate(" + d.x + "," + d.y + ")"; });
|
||||||
}
|
}
|
||||||
ctrl.done(json);
|
slice.done(json);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
@ -158,4 +156,4 @@ function viz_directed_force(data_attribute) {
|
||||||
resize: render,
|
resize: render,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
px.registerWidget('directed_force', viz_directed_force);
|
px.registerViz('directed_force', viz_directed_force);
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
px.registerWidget('markup', function(data_attribute) {
|
px.registerViz('markup', function(slice) {
|
||||||
|
|
||||||
function refresh(ctrl) {
|
function refresh() {
|
||||||
$('#code').attr('rows', '15')
|
$('#code').attr('rows', '15')
|
||||||
ctrl.done();
|
$.getJSON(slice.jsonEndpoint(), function(payload) {
|
||||||
}
|
slice.container.html(payload.data.html);
|
||||||
|
slice.done();
|
||||||
|
})
|
||||||
|
.fail(function(xhr) {
|
||||||
|
slice.error(xhr.responseText);
|
||||||
|
});
|
||||||
|
};
|
||||||
return {
|
return {
|
||||||
render: refresh,
|
render: refresh,
|
||||||
resize: refresh,
|
resize: refresh,
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
function viz_nvd3(data_attribute) {
|
function viz_nvd3(slice) {
|
||||||
var token_name = data_attribute['token'];
|
|
||||||
var token = d3.select('#' + token_name);
|
|
||||||
var json_callback = data_attribute['json_endpoint'];
|
|
||||||
var chart = undefined;
|
var chart = undefined;
|
||||||
var data = {};
|
var data = {};
|
||||||
|
|
||||||
|
@ -26,15 +23,13 @@ function viz_nvd3(data_attribute) {
|
||||||
"#FF5A5F", "#007A87", "#7B0051", "#00D1C1", "#8CE071", "#FFB400",
|
"#FF5A5F", "#007A87", "#7B0051", "#00D1C1", "#8CE071", "#FFB400",
|
||||||
"#FFAA91", "#B4A76C", "#9CA299", "#565A5C"
|
"#FFAA91", "#B4A76C", "#9CA299", "#565A5C"
|
||||||
];
|
];
|
||||||
var jtoken = $('#' + token_name);
|
var refresh = function() {
|
||||||
var chart_div = $('#' + token_name).find("div.chart");
|
$.getJSON(slice.jsonEndpoint(), function(payload) {
|
||||||
|
|
||||||
var refresh = function(ctrl) {
|
|
||||||
chart_div.hide();
|
|
||||||
$.getJSON(json_callback, function(payload) {
|
|
||||||
var data = payload.data;
|
var data = payload.data;
|
||||||
var viz = payload;
|
var viz = payload;
|
||||||
var viz_type = viz.form_data.viz_type;
|
var viz_type = viz.form_data.viz_type;
|
||||||
|
var fd = viz.form_data;
|
||||||
|
var f = d3.format('.4s');
|
||||||
nv.addGraph(function() {
|
nv.addGraph(function() {
|
||||||
if (viz_type === 'line') {
|
if (viz_type === 'line') {
|
||||||
if (viz.form_data.show_brush) {
|
if (viz.form_data.show_brush) {
|
||||||
|
@ -113,10 +108,25 @@ function viz_nvd3(data_attribute) {
|
||||||
chart.yAxis.tickFormat(d3.format('.3p'));
|
chart.yAxis.tickFormat(d3.format('.3p'));
|
||||||
|
|
||||||
} else if (viz_type === 'bubble') {
|
} else if (viz_type === 'bubble') {
|
||||||
|
var row = function(col1, col2){
|
||||||
|
return "<tr><td>" + col1 + "</td><td>" + col2 + "</td></r>"
|
||||||
|
}
|
||||||
chart = nv.models.scatterChart();
|
chart = nv.models.scatterChart();
|
||||||
|
chart.showDistX(true);
|
||||||
|
chart.showDistY(true);
|
||||||
chart.xAxis.tickFormat(d3.format('.3s'));
|
chart.xAxis.tickFormat(d3.format('.3s'));
|
||||||
chart.yAxis.tickFormat(d3.format('.3s'));
|
chart.yAxis.tickFormat(d3.format('.3s'));
|
||||||
chart.showLegend(viz.form_data.show_legend);
|
chart.showLegend(fd.show_legend);
|
||||||
|
chart.tooltip.contentGenerator(function (obj) {
|
||||||
|
p = obj.point;
|
||||||
|
var s = "<table>"
|
||||||
|
s += '<tr><td style="color:' + p.color + ';"><strong>' + p[fd.entity] + '</strong> (' + p.group + ')</td></tr>';
|
||||||
|
s += row(fd.x, f(p.x));
|
||||||
|
s += row(fd.y, f(p.y));
|
||||||
|
s += row(fd.size, f(p.size));
|
||||||
|
s += "</table>";
|
||||||
|
return s;
|
||||||
|
});
|
||||||
chart.pointRange([5, 5000]);
|
chart.pointRange([5, 5000]);
|
||||||
|
|
||||||
} else if (viz_type === 'area') {
|
} else if (viz_type === 'area') {
|
||||||
|
@ -130,7 +140,7 @@ function viz_nvd3(data_attribute) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// make space for labels on right
|
// make space for labels on right
|
||||||
chart.height($(".chart").height() - 50).margin({"right": 50});
|
//chart.height($(".chart").height() - 50).margin({"right": 50});
|
||||||
if ((viz_type === "line" || viz_type === "area") && viz.form_data.rich_tooltip) {
|
if ((viz_type === "line" || viz_type === "area") && viz.form_data.rich_tooltip) {
|
||||||
chart.useInteractiveGuideline(true);
|
chart.useInteractiveGuideline(true);
|
||||||
}
|
}
|
||||||
|
@ -153,19 +163,17 @@ function viz_nvd3(data_attribute) {
|
||||||
|
|
||||||
chart.duration(0);
|
chart.duration(0);
|
||||||
|
|
||||||
token.select('.chart').append("svg")
|
d3.select(slice.selector).append("svg")
|
||||||
.datum(data.chart_data)
|
.datum(data.chart_data)
|
||||||
.transition().duration(500)
|
.transition().duration(500)
|
||||||
.call(chart);
|
.call(chart);
|
||||||
|
|
||||||
return chart;
|
return chart;
|
||||||
});
|
});
|
||||||
chart_div.show();
|
slice.done(data);
|
||||||
ctrl.done(data);
|
|
||||||
})
|
})
|
||||||
.fail(function(xhr) {
|
.fail(function(xhr) {
|
||||||
chart_div.show();
|
slice.error(xhr.responseText);
|
||||||
ctrl.error(xhr.responseText);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
var resize = function() {
|
var resize = function() {
|
||||||
|
@ -189,5 +197,5 @@ function viz_nvd3(data_attribute) {
|
||||||
'line',
|
'line',
|
||||||
'pie',
|
'pie',
|
||||||
].forEach(function(name) {
|
].forEach(function(name) {
|
||||||
px.registerWidget(name, viz_nvd3);
|
px.registerViz(name, viz_nvd3);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
li.widget.pivot_table div.token {
|
li.widget.pivot_table div.slice_container {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,20 @@
|
||||||
px.registerWidget('pivot_table', function(data_attribute) {
|
px.registerViz('pivot_table', function(slice) {
|
||||||
var token_name = data_attribute['token'];
|
container = slice.container;
|
||||||
var token = $('#' + token_name);
|
var form_data = slice.data.form_data;
|
||||||
var form_data = data_attribute.form_data;
|
|
||||||
|
|
||||||
function refresh(ctrl) {
|
function refresh() {
|
||||||
$.getJSON(data_attribute.json_endpoint, function(json){
|
$.getJSON(slice.jsonEndpoint(), function(json){
|
||||||
token.html(json.data);
|
container.html(json.data);
|
||||||
if (form_data.groupby.length == 1){
|
if (form_data.groupby.length == 1){
|
||||||
var table = token.find('table').DataTable({
|
var table = container.find('table').DataTable({
|
||||||
paging: false,
|
paging: false,
|
||||||
searching: false,
|
searching: false,
|
||||||
});
|
});
|
||||||
table.column('-1').order( 'desc' ).draw();
|
table.column('-1').order( 'desc' ).draw();
|
||||||
}
|
}
|
||||||
token.show();
|
slice.done(json);
|
||||||
ctrl.done(json);
|
|
||||||
}).fail(function(xhr){
|
}).fail(function(xhr){
|
||||||
token.show();
|
slice.error(xhr.responseText);
|
||||||
ctrl.error(xhr.responseText);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,28 +1,21 @@
|
||||||
#chart {
|
.sankey .node rect {
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
div.token {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.node rect {
|
|
||||||
cursor: move;
|
cursor: move;
|
||||||
fill-opacity: .9;
|
fill-opacity: .9;
|
||||||
shape-rendering: crispEdges;
|
shape-rendering: crispEdges;
|
||||||
}
|
}
|
||||||
|
|
||||||
.node text {
|
.sankey .node text {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
text-shadow: 0 1px 0 #fff;
|
text-shadow: 0 1px 0 #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.link {
|
.sankey .link {
|
||||||
fill: none;
|
fill: none;
|
||||||
stroke: #000;
|
stroke: #000;
|
||||||
stroke-opacity: .2;
|
stroke-opacity: .2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.link:hover {
|
.sankey .link:hover {
|
||||||
stroke-opacity: .5;
|
stroke-opacity: .5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
function viz_sankey(data_attribute) {
|
function viz_sankey(slice) {
|
||||||
var token = d3.select('#' + data_attribute.token);
|
var div = d3.select(slice.selector);
|
||||||
var div = token.select("#chart");
|
|
||||||
var xy = div.node().getBoundingClientRect();
|
|
||||||
var width = xy.width;
|
|
||||||
var height = xy.height - 25;
|
|
||||||
|
|
||||||
var render = function(ctrl) {
|
var render = function() {
|
||||||
var margin = {top: 1, right: 1, bottom: 6, left: 1};
|
var width = slice.container.width();
|
||||||
|
var height = slice.container.height() - 25;
|
||||||
|
var margin = {top: 5, right: 5, bottom: 5, left: 5};
|
||||||
width = width - margin.left - margin.right;
|
width = width - margin.left - margin.right;
|
||||||
height = height - margin.top - margin.bottom;
|
height = height - margin.top - margin.bottom;
|
||||||
|
|
||||||
|
@ -14,7 +12,7 @@ function viz_sankey(data_attribute) {
|
||||||
format = function(d) { return formatNumber(d) + " TWh"; },
|
format = function(d) { return formatNumber(d) + " TWh"; },
|
||||||
color = d3.scale.category20();
|
color = d3.scale.category20();
|
||||||
|
|
||||||
var svg = token.select("#chart").append("svg")
|
var svg = div.append("svg")
|
||||||
.attr("width", width + margin.left + margin.right)
|
.attr("width", width + margin.left + margin.right)
|
||||||
.attr("height", height + margin.top + margin.bottom)
|
.attr("height", height + margin.top + margin.bottom)
|
||||||
.append("g")
|
.append("g")
|
||||||
|
@ -26,9 +24,9 @@ function viz_sankey(data_attribute) {
|
||||||
|
|
||||||
var path = sankey.link();
|
var path = sankey.link();
|
||||||
|
|
||||||
d3.json(data_attribute.json_endpoint, function(error, json) {
|
d3.json(slice.data.json_endpoint, function(error, json) {
|
||||||
if (error != null){
|
if (error != null){
|
||||||
ctrl.error(error.responseText);
|
slice.error(error.responseText);
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
links = json.data;
|
links = json.data;
|
||||||
|
@ -95,8 +93,7 @@ function viz_sankey(data_attribute) {
|
||||||
sankey.relayout();
|
sankey.relayout();
|
||||||
link.attr("d", path);
|
link.attr("d", path);
|
||||||
}
|
}
|
||||||
token.select("img.loading").remove();
|
slice.done(json);
|
||||||
ctrl.done(json);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
@ -104,4 +101,4 @@ function viz_sankey(data_attribute) {
|
||||||
resize: render,
|
resize: render,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
px.registerWidget('sankey', viz_sankey);
|
px.registerViz('sankey', viz_sankey);
|
||||||
|
|
|
@ -1,32 +1,19 @@
|
||||||
#sidebar {
|
.sunburst text.middle{
|
||||||
float: right;
|
|
||||||
width: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
text.middle{
|
|
||||||
text-anchor: middle;
|
text-anchor: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
#sequence {
|
.sunburst #sequence {
|
||||||
}
|
}
|
||||||
|
|
||||||
#legend {
|
.sunburst #legend {
|
||||||
padding: 10px 0 0 3px;
|
padding: 10px 0 0 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#sequence text, #legend text {
|
.sunburst #sequence text, #legend text {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
fill: #fff;
|
fill: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sunburst #chart {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sunburst div.token {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sunburst path {
|
.sunburst path {
|
||||||
stroke: #fff;
|
stroke: #fff;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
Modified from http://bl.ocks.org/kerryrodden/7090426
|
Modified from http://bl.ocks.org/kerryrodden/7090426
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function viz_sunburst(data_attribute) {
|
function viz_sunburst(slice) {
|
||||||
var token = d3.select('#' + data_attribute.token);
|
var container = d3.select(slice.selector);
|
||||||
var render = function(ctrl) {
|
var render = function() {
|
||||||
// Breadcrumb dimensions: width, height, spacing, width of tip/tail.
|
// Breadcrumb dimensions: width, height, spacing, width of tip/tail.
|
||||||
var b = {
|
var b = {
|
||||||
w: 100, h: 30, s: 3, t: 10
|
w: 100, h: 30, s: 3, t: 10
|
||||||
|
@ -13,13 +13,11 @@ function viz_sunburst(data_attribute) {
|
||||||
|
|
||||||
// Total size of all segments; we set this later, after loading the data.
|
// Total size of all segments; we set this later, after loading the data.
|
||||||
var totalSize = 0;
|
var totalSize = 0;
|
||||||
var div = token.select("#chart");
|
var width = slice.container.width();
|
||||||
var xy = div.node().getBoundingClientRect();
|
var height = slice.container.height() - 25;
|
||||||
var width = xy.width;
|
|
||||||
var height = xy.height - 25;
|
|
||||||
var radius = Math.min(width, height) / 2;
|
var radius = Math.min(width, height) / 2;
|
||||||
|
|
||||||
var vis = div.append("svg:svg")
|
var vis = container.append("svg:svg")
|
||||||
.attr("width", width)
|
.attr("width", width)
|
||||||
.attr("height", height)
|
.attr("height", height)
|
||||||
.append("svg:g")
|
.append("svg:g")
|
||||||
|
@ -39,15 +37,15 @@ function viz_sunburst(data_attribute) {
|
||||||
.outerRadius(function(d) { return Math.sqrt(d.y + d.dy); });
|
.outerRadius(function(d) { return Math.sqrt(d.y + d.dy); });
|
||||||
|
|
||||||
var ext;
|
var ext;
|
||||||
d3.json(data_attribute.json_endpoint, function(error, json){
|
d3.json(slice.data.json_endpoint, function(error, json){
|
||||||
|
|
||||||
if (error != null){
|
if (error != null){
|
||||||
ctrl.error(error.responseText);
|
slice.error(error.responseText);
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
var tree = buildHierarchy(json.data);
|
var tree = buildHierarchy(json.data);
|
||||||
createVisualization(tree);
|
createVisualization(tree);
|
||||||
ctrl.done(json);
|
slice.done(json);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Main function to draw and set up the visualization, once we have the data.
|
// Main function to draw and set up the visualization, once we have the data.
|
||||||
|
@ -84,7 +82,7 @@ function viz_sunburst(data_attribute) {
|
||||||
|
|
||||||
|
|
||||||
// Add the mouseleave handler to the bounding circle.
|
// Add the mouseleave handler to the bounding circle.
|
||||||
token.select("#container").on("mouseleave", mouseleave);
|
container.select("#container").on("mouseleave", mouseleave);
|
||||||
|
|
||||||
// Get total size of the tree = value of root node from partition.
|
// Get total size of the tree = value of root node from partition.
|
||||||
totalSize = path.node().__data__.value;
|
totalSize = path.node().__data__.value;
|
||||||
|
@ -117,12 +115,12 @@ function viz_sunburst(data_attribute) {
|
||||||
updateBreadcrumbs(sequenceArray, percentageString);
|
updateBreadcrumbs(sequenceArray, percentageString);
|
||||||
|
|
||||||
// Fade all the segments.
|
// Fade all the segments.
|
||||||
token.selectAll("path")
|
container.selectAll("path")
|
||||||
.style("stroke-width", "1px")
|
.style("stroke-width", "1px")
|
||||||
.style("opacity", 0.3);
|
.style("opacity", 0.3);
|
||||||
|
|
||||||
// Then highlight only those that are an ancestor of the current segment.
|
// Then highlight only those that are an ancestor of the current segment.
|
||||||
token.selectAll("path")
|
container.selectAll("path")
|
||||||
.filter(function(node) {
|
.filter(function(node) {
|
||||||
return (sequenceArray.indexOf(node) >= 0);
|
return (sequenceArray.indexOf(node) >= 0);
|
||||||
})
|
})
|
||||||
|
@ -135,16 +133,16 @@ function viz_sunburst(data_attribute) {
|
||||||
function mouseleave(d) {
|
function mouseleave(d) {
|
||||||
|
|
||||||
// Hide the breadcrumb trail
|
// Hide the breadcrumb trail
|
||||||
token.select("#trail")
|
container.select("#trail")
|
||||||
.style("visibility", "hidden");
|
.style("visibility", "hidden");
|
||||||
gMiddleText.selectAll("*").remove();
|
gMiddleText.selectAll("*").remove();
|
||||||
|
|
||||||
// Deactivate all segments during transition.
|
// Deactivate all segments during transition.
|
||||||
token.selectAll("path").on("mouseenter", null);
|
container.selectAll("path").on("mouseenter", null);
|
||||||
//gMiddleText.selectAll("*").remove();
|
//gMiddleText.selectAll("*").remove();
|
||||||
|
|
||||||
// Transition each segment to full opacity and then reactivate it.
|
// Transition each segment to full opacity and then reactivate it.
|
||||||
token.selectAll("path")
|
container.selectAll("path")
|
||||||
.transition()
|
.transition()
|
||||||
.duration(200)
|
.duration(200)
|
||||||
.style("opacity", 1)
|
.style("opacity", 1)
|
||||||
|
@ -253,4 +251,4 @@ function viz_sunburst(data_attribute) {
|
||||||
resize: render,
|
resize: render,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
px.registerWidget('sunburst', viz_sunburst);
|
px.registerViz('sunburst', viz_sunburst);
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
li.widget.table div.token {
|
li.widget.table div.token {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
td.filtered {
|
||||||
|
background-color: #005a63;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
px.registerWidget('table', function(data_attribute) {
|
px.registerViz('table', function(slice) {
|
||||||
|
var data = slice.data;
|
||||||
|
var form_data = data.form_data;
|
||||||
|
|
||||||
var token_name = data_attribute['token'];
|
function refresh() {
|
||||||
var token = $('#' + token_name);
|
var f = d3.format('.3s');
|
||||||
|
$.getJSON(slice.jsonEndpoint(), function(json){
|
||||||
function refresh(ctrl) {
|
|
||||||
$.getJSON(data_attribute.json_endpoint, function(json){
|
|
||||||
var data = json.data;
|
var data = json.data;
|
||||||
var metrics = json.form_data.metrics;
|
var metrics = json.form_data.metrics;
|
||||||
function col(c){
|
function col(c){
|
||||||
|
@ -18,8 +18,7 @@ px.registerWidget('table', function(data_attribute) {
|
||||||
for (var i=0; i<metrics.length; i++){
|
for (var i=0; i<metrics.length; i++){
|
||||||
maxes[metrics[i]] = d3.max(col(metrics[i]));
|
maxes[metrics[i]] = d3.max(col(metrics[i]));
|
||||||
}
|
}
|
||||||
|
var table = d3.select(slice.selector).append('table')
|
||||||
var table = d3.select('#' + token_name).append('table')
|
|
||||||
.attr('class', 'dataframe table table-striped table-bordered table-condensed table-hover');
|
.attr('class', 'dataframe table table-striped table-bordered table-condensed table-hover');
|
||||||
table.append('thead').append('tr')
|
table.append('thead').append('tr')
|
||||||
.selectAll('th')
|
.selectAll('th')
|
||||||
|
@ -32,27 +31,57 @@ px.registerWidget('table', function(data_attribute) {
|
||||||
.append('tr')
|
.append('tr')
|
||||||
.selectAll('td')
|
.selectAll('td')
|
||||||
.data(function(row, i) {
|
.data(function(row, i) {
|
||||||
return data.columns.map(function(c) {return [c, row];});
|
return data.columns.map(function(c) {
|
||||||
|
return {col: c, val: row[c], isMetric: metrics.indexOf(c) >=0};
|
||||||
|
});
|
||||||
}).enter()
|
}).enter()
|
||||||
.append('td')
|
.append('td')
|
||||||
.style('background-image', function(d){
|
.style('background-image', function(d){
|
||||||
var perc = Math.round((d[1][d[0]] / maxes[d[0]]) * 100);
|
if (d.isMetric){
|
||||||
if (perc !== NaN)
|
var perc = Math.round((d.val / maxes[d.col]) * 100);
|
||||||
return "linear-gradient(to right, lightgrey, lightgrey " + perc + "%, rgba(0,0,0,0) " + perc + "%"
|
return "linear-gradient(to right, lightgrey, lightgrey " + perc + "%, rgba(0,0,0,0) " + perc + "%";
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.html(function(d){return d[1][d[0]]});
|
.attr('data-sort', function(d){
|
||||||
var datatable = token.find('table').DataTable({
|
if (d.isMetric)
|
||||||
|
return d.val;
|
||||||
|
})
|
||||||
|
.on("click", function(d){
|
||||||
|
if(!d.isMetric){
|
||||||
|
var td = d3.select(this);
|
||||||
|
if (td.classed('filtered')){
|
||||||
|
slice.clearFilter(d.col, [d.val]);
|
||||||
|
table.selectAll('.filtered').classed('filtered', false);
|
||||||
|
} else {
|
||||||
|
table.selectAll('.filtered').classed('filtered', false);
|
||||||
|
d3.select(this).classed('filtered', true);
|
||||||
|
slice.addFilter(d.col, [d.val]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.style("cursor", function(d){
|
||||||
|
if(!d.isMetric){
|
||||||
|
return 'pointer';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.html(function(d){
|
||||||
|
if (d.isMetric)
|
||||||
|
return f(d.val);
|
||||||
|
else
|
||||||
|
return d.val;
|
||||||
|
});
|
||||||
|
var datatable = slice.container.find('table').DataTable({
|
||||||
paging: false,
|
paging: false,
|
||||||
searching: data_attribute.form_data.include_search,
|
searching: form_data.include_search,
|
||||||
});
|
});
|
||||||
// Sorting table by main column
|
// Sorting table by main column
|
||||||
if (data_attribute.form_data.metrics.length > 0) {
|
if (form_data.metrics.length > 0) {
|
||||||
var main_metric = data_attribute.form_data.metrics[0];
|
var main_metric = form_data.metrics[0];
|
||||||
datatable.column(data.columns.indexOf(main_metric)).order( 'desc' ).draw();
|
datatable.column(data.columns.indexOf(main_metric)).order( 'desc' ).draw();
|
||||||
}
|
}
|
||||||
ctrl.done(json);
|
slice.done(json);
|
||||||
}).fail(function(xhr){
|
}).fail(function(xhr){
|
||||||
ctrl.error(xhr.responseText);
|
slice.error(xhr.responseText);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
px.registerWidget('word_cloud', function(data_attribute) {
|
px.registerViz('word_cloud', function(slice) {
|
||||||
|
var slice = slice;
|
||||||
var token_name = data_attribute['token'];
|
var chart = d3.select(slice.selector);
|
||||||
var json_callback = data_attribute['json_endpoint'];
|
function refresh() {
|
||||||
var token = d3.select('#' + token_name);
|
d3.json(slice.jsonEndpoint(), function(error, json) {
|
||||||
|
|
||||||
function refresh(ctrl) {
|
|
||||||
d3.json(json_callback, function(error, json) {
|
|
||||||
if (error != null){
|
if (error != null){
|
||||||
ctrl.error(error.responseText);
|
slice.error(error.responseText);
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
var data = json.data;
|
var data = json.data;
|
||||||
|
@ -25,8 +22,7 @@ px.registerWidget('word_cloud', function(data_attribute) {
|
||||||
else {
|
else {
|
||||||
var f_rotation = function() { return (~~(Math.random() * 6) - 3) * 30; };
|
var f_rotation = function() { return (~~(Math.random() * 6) - 3) * 30; };
|
||||||
}
|
}
|
||||||
var box = token.node().getBoundingClientRect();
|
var size = [slice.container.width(), slice.container.height() - 25];
|
||||||
var size = [box.width, box.height - 25];
|
|
||||||
|
|
||||||
scale = d3.scale.linear()
|
scale = d3.scale.linear()
|
||||||
.range(range)
|
.range(range)
|
||||||
|
@ -42,9 +38,9 @@ px.registerWidget('word_cloud', function(data_attribute) {
|
||||||
.on("end", draw);
|
.on("end", draw);
|
||||||
layout.start();
|
layout.start();
|
||||||
function draw(words) {
|
function draw(words) {
|
||||||
token.selectAll("*").remove();
|
chart.selectAll("*").remove();
|
||||||
|
|
||||||
token.append("svg")
|
chart.append("svg")
|
||||||
.attr("width", layout.size()[0])
|
.attr("width", layout.size()[0])
|
||||||
.attr("height", layout.size()[1])
|
.attr("height", layout.size()[1])
|
||||||
.append("g")
|
.append("g")
|
||||||
|
@ -61,7 +57,7 @@ px.registerWidget('word_cloud', function(data_attribute) {
|
||||||
})
|
})
|
||||||
.text(function(d) { return d.text; });
|
.text(function(d) { return d.text; });
|
||||||
}
|
}
|
||||||
ctrl.done(data);
|
slice.done(data);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,5 +65,4 @@ px.registerWidget('word_cloud', function(data_attribute) {
|
||||||
render: refresh,
|
render: refresh,
|
||||||
resize: refresh,
|
resize: refresh,
|
||||||
};
|
};
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
.world_map svg{
|
||||||
|
background-color: LightSkyBlue;
|
||||||
|
}
|
|
@ -2,26 +2,22 @@
|
||||||
Using the awesome lib at http://datamaps.github.io/
|
Using the awesome lib at http://datamaps.github.io/
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function viz_world_map(data_attribute) {
|
function viz_world_map(slice) {
|
||||||
var token = d3.select('#' + data_attribute.token);
|
var render = function() {
|
||||||
var render = function(ctrl) {
|
var container = slice.container;
|
||||||
// Breadcrumb dimensions: width, height, spacing, width of tip/tail.
|
var div = d3.select(slice.selector);
|
||||||
var div = token;
|
|
||||||
var xy = div.node().getBoundingClientRect();
|
|
||||||
var width = xy.width;
|
|
||||||
var height = xy.height - 25;
|
|
||||||
|
|
||||||
d3.json(data_attribute.json_endpoint, function(error, json){
|
d3.json(slice.data.json_endpoint, function(error, json){
|
||||||
|
|
||||||
if (error != null){
|
if (error != null){
|
||||||
ctrl.error(error.responseText);
|
slice.error(error.responseText);
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
var ext = d3.extent(json.data, function(d){return d.m1});
|
var ext = d3.extent(json.data, function(d){return d.m1});
|
||||||
var extRadius = d3.extent(json.data, function(d){return d.m2});
|
var extRadius = d3.extent(json.data, function(d){return d.m2});
|
||||||
var radiusScale = d3.scale.linear()
|
var radiusScale = d3.scale.linear()
|
||||||
.domain([extRadius[0], extRadius[1]])
|
.domain([extRadius[0], extRadius[1]])
|
||||||
.range([1, data_attribute.form_data.max_bubble_size]);
|
.range([1, slice.data.form_data.max_bubble_size]);
|
||||||
json.data.forEach(function(d){
|
json.data.forEach(function(d){
|
||||||
d.radius = radiusScale(d.m2);
|
d.radius = radiusScale(d.m2);
|
||||||
})
|
})
|
||||||
|
@ -35,11 +31,12 @@ function viz_world_map(data_attribute) {
|
||||||
d[country.country] = country;
|
d[country.country] = country;
|
||||||
}
|
}
|
||||||
f = d3.format('.3s');
|
f = d3.format('.3s');
|
||||||
|
container.show();
|
||||||
var map = new Datamap({
|
var map = new Datamap({
|
||||||
element: document.getElementById(data_attribute.token),
|
element: slice.container.get(0),
|
||||||
data: json.data,
|
data: json.data,
|
||||||
fills: {
|
fills: {
|
||||||
defaultFill: 'grey'
|
defaultFill: 'transparent'
|
||||||
},
|
},
|
||||||
geographyConfig: {
|
geographyConfig: {
|
||||||
popupOnHover: true,
|
popupOnHover: true,
|
||||||
|
@ -75,11 +72,11 @@ function viz_world_map(data_attribute) {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
map.updateChoropleth(d);
|
map.updateChoropleth(d);
|
||||||
if(data_attribute.form_data.show_bubbles){
|
if(slice.data.form_data.show_bubbles){
|
||||||
map.bubbles(json.data);
|
map.bubbles(json.data);
|
||||||
token.selectAll("circle.datamaps-bubble").style('fill', '#005a63');
|
div.selectAll("circle.datamaps-bubble").style('fill', '#005a63');
|
||||||
}
|
}
|
||||||
ctrl.done(json);
|
slice.done(json);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,4 +85,4 @@ function viz_world_map(data_attribute) {
|
||||||
resize: render,
|
resize: render,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
px.registerWidget('world_map', viz_world_map);
|
px.registerViz('world_map', viz_world_map);
|
||||||
|
|
|
@ -9,11 +9,6 @@
|
||||||
<style id="user_style" type="text/css">
|
<style id="user_style" type="text/css">
|
||||||
{{ dashboard.css }}
|
{{ dashboard.css }}
|
||||||
</style>
|
</style>
|
||||||
{% for slice in dashboard.slices %}
|
|
||||||
{% set viz = slice.viz %}
|
|
||||||
{% import viz.template as viz_macros %}
|
|
||||||
{{ viz_macros.viz_css(viz) }}
|
|
||||||
{% endfor %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content_fluid %}
|
{% block content_fluid %}
|
||||||
|
@ -77,17 +72,16 @@ body {
|
||||||
{% for slice in dashboard.slices %}
|
{% for slice in dashboard.slices %}
|
||||||
{% set pos = pos_dict.get(slice.id, {}) %}
|
{% set pos = pos_dict.get(slice.id, {}) %}
|
||||||
{% set viz = slice.viz %}
|
{% set viz = slice.viz %}
|
||||||
{% import viz.template as viz_macros %}
|
|
||||||
<li
|
<li
|
||||||
id="slice_{{ slice.id }}"
|
id="slice_{{ slice.id }}"
|
||||||
slice_id="{{ slice.id }}"
|
slice_id="{{ slice.id }}"
|
||||||
class="widget {{ slice.viz.viz_type }}"
|
class="widget {{ slice.viz.viz_type }}"
|
||||||
data-widget="{{ slice.viz.get_data_attribute() }}"
|
data-slice="{{ slice.json_data }}"
|
||||||
data-row="{{ pos.row or 1 }}"
|
data-row="{{ pos.row or 1 }}"
|
||||||
data-col="{{ pos.col or loop.index }}"
|
data-col="{{ pos.col or loop.index }}"
|
||||||
data-sizex="{{ pos.size_x or 4 }}"
|
data-sizex="{{ pos.size_x or 4 }}"
|
||||||
data-sizey="{{ pos.size_y or 4 }}">
|
data-sizey="{{ pos.size_y or 4 }}">
|
||||||
<table class="widget_header">
|
<table class="slice_header">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="icons">
|
<td class="icons">
|
||||||
|
@ -103,7 +97,7 @@ body {
|
||||||
<nobr>
|
<nobr>
|
||||||
<a href="{{ slice.slice_url }}"><i class="fa fa-play"></i></a>
|
<a href="{{ slice.slice_url }}"><i class="fa fa-play"></i></a>
|
||||||
<a href="{{ slice.edit_url }}"><i class="fa fa-gear"></i></a>
|
<a href="{{ slice.edit_url }}"><i class="fa fa-gear"></i></a>
|
||||||
<a class="closewidget"><i class="fa fa-close"></i></a>
|
<a class="closeslice"><i class="fa fa-close"></i></a>
|
||||||
</br>
|
</br>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -111,7 +105,7 @@ body {
|
||||||
</table>
|
</table>
|
||||||
<div id="{{ viz.token }}" class="token" style="height: 100%;">
|
<div id="{{ viz.token }}" class="token" style="height: 100%;">
|
||||||
<img src="{{ url_for("static", filename="img/loading.gif") }}" class="loading" alt="loading">
|
<img src="{{ url_for("static", filename="img/loading.gif") }}" class="loading" alt="loading">
|
||||||
{{ viz_macros.viz_html(viz) }}
|
<div class="slice_container" id="{{ viz.token }}_con" style="height: 100%; width: 100%;"></div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -127,19 +121,9 @@ body {
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<script src="/static/lib/gridster/jquery.gridster.with-extras.min.js"></script>
|
<script src="/static/lib/gridster/jquery.gridster.with-extras.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(px.initializeDashboardView({{ dashboard.id }}));
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
$('.dashboard .widget').each(function() {
|
px.initDashboardView();
|
||||||
var data = $(this).data('widget');
|
var dashboard = px.Dashboard({{ dashboard.id }});
|
||||||
var widget = px.initializeWidget(data);
|
|
||||||
$(this).data('widget', widget);
|
|
||||||
widget.render();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% for slice in dashboard.slices %}
|
|
||||||
{% set viz = slice.viz %}
|
|
||||||
{% import viz.template as viz_macros %}
|
|
||||||
{{ viz_macros.viz_js(viz) }}
|
|
||||||
{% endfor %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -26,22 +26,29 @@
|
||||||
<span class="btn btn-default notbtn">
|
<span class="btn btn-default notbtn">
|
||||||
<strong>{{ datasource.full_name }}</strong>
|
<strong>{{ datasource.full_name }}</strong>
|
||||||
{% if datasource.description %}
|
{% if datasource.description %}
|
||||||
|
<a data-toggle="modal" data-target="#sourceinfo_modal">
|
||||||
<i class="fa fa-info-circle" data-toggle="tooltip" data-placement="bottom" title="{{ datasource.description }}"></i>
|
<i class="fa fa-info-circle" data-toggle="tooltip" data-placement="bottom" title="{{ datasource.description }}"></i>
|
||||||
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a class="" href="/{{ datasource.baselink }}/edit/{{ datasource.id }}" data-toggle="tooltip" title="Edit datasource">
|
<a class="" href="/{{ datasource.baselink }}/edit/{{ datasource.id }}" data-toggle="tooltip" title="Edit datasource">
|
||||||
<i class="fa fa-edit"></i>
|
<i class="fa fa-edit"></i>
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
<span>{{ form.get_field("viz_type")(class_="select2") }}</span>
|
<span>{{ form.get_field("viz_type")(class_="select2") }}</span>
|
||||||
<span class="btn btn-info pull-right disabled query"
|
|
||||||
data-toggle="modal" data-target="#query_modal">query</span>
|
|
||||||
<span class="btn btn-warning pull-right notbtn" id="timer">0 sec</span>
|
|
||||||
<div class="btn-group pull-right" role="group">
|
<div class="btn-group pull-right" role="group">
|
||||||
<span class="btn btn-default disabled">
|
<span class="btn btn-default" id="standalone" title="Standalone version, use to embed anywhere" data-toggle="tooltip">
|
||||||
<i class="fa fa-file-text"></i>
|
<i class="fa fa-code"></i>
|
||||||
</span>
|
</span>
|
||||||
<span class="btn btn-default" id="csv">.csv</span>
|
<span class="btn btn-default " id="json" title="Export to .json" data-toggle="tooltip">
|
||||||
<span class="btn btn-default" id="json">.json</span>
|
<i class="fa fa-file-code-o"></i>
|
||||||
|
.json
|
||||||
|
</span>
|
||||||
|
<span class="btn btn-default " id="csv" title="Export to .csv format" data-toggle="tooltip">
|
||||||
|
<i class="fa fa-file-text-o"></i>.csv
|
||||||
|
</span>
|
||||||
|
<span class="btn btn-warning notbtn" id="timer">0 sec</span>
|
||||||
|
<span class="btn btn-info disabled query"
|
||||||
|
data-toggle="modal" data-target="#query_modal">query</span>
|
||||||
</div>
|
</div>
|
||||||
<hr/>
|
<hr/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -133,23 +140,15 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
{% block messages %}
|
{% block messages %}{% endblock %}
|
||||||
{% endblock %}
|
|
||||||
{% include 'appbuilder/flash.html' %}
|
{% include 'appbuilder/flash.html' %}
|
||||||
<div
|
<div
|
||||||
id="{{ viz.token }}"
|
id="{{ viz.token }}"
|
||||||
class="viz widget {{ viz.viz_type }}"
|
class="viz slice {{ viz.viz_type }}"
|
||||||
data-widget="{{ viz.get_data_attribute() }}"
|
data-slice="{{ viz.json_data }}"
|
||||||
style="height: 700px;">
|
style="height: 700px;">
|
||||||
<img src="{{ url_for("static", filename="img/loading.gif") }}" class="loading" alt="loading">
|
<img src="{{ url_for("static", filename="img/loading.gif") }}" class="loading" alt="loading">
|
||||||
{% block viz_html %}
|
<div id="{{ viz.token }}_con" class="slice_container" style="height: 100%; width: 100%"></div>
|
||||||
{% if viz.error_msg %}
|
|
||||||
<div class="alert alert-danger">{{ viz.error_msg }}</div>
|
|
||||||
{% endif %}
|
|
||||||
{% if viz.warning_msg %}
|
|
||||||
<div class="alert alert-warning">{{ viz.warning_msg }}</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -176,6 +175,24 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="modal fade" id="sourceinfo_modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
|
||||||
|
<div class="modal-dialog modal-lg" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
<h4 class="modal-title">Query</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<pre id="query_container">{{ datasource.description }}</pre>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -183,15 +200,15 @@
|
||||||
{% block tail_js %}
|
{% block tail_js %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(px.initializeDatasourceView);
|
$(document).ready(px.initExploreView);
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
var data = $('.widget').data('widget');
|
var data = $('.slice').data('slice');
|
||||||
var widget = px.initializeWidget(data);
|
var slice = px.Slice(data);
|
||||||
$('.widget').data('widget', widget);
|
$('.slice').data('slice', slice);
|
||||||
widget.render();
|
slice.render();
|
||||||
|
|
||||||
function get_collapsed_fieldsets(){
|
function get_collapsed_fieldsets(){
|
||||||
collapsed_fieldsets = $("#collapsed_fieldsets").val()
|
collapsed_fieldsets = $("#collapsed_fieldsets").val();
|
||||||
if (collapsed_fieldsets != undefined && collapsed_fieldsets != "")
|
if (collapsed_fieldsets != undefined && collapsed_fieldsets != "")
|
||||||
collapsed_fieldsets = collapsed_fieldsets.split('||');
|
collapsed_fieldsets = collapsed_fieldsets.split('||');
|
||||||
else
|
else
|
||||||
|
@ -242,6 +259,9 @@
|
||||||
$('#csv').click(function () {
|
$('#csv').click(function () {
|
||||||
window.location = '{{ viz.csv_endpoint | safe }}';
|
window.location = '{{ viz.csv_endpoint | safe }}';
|
||||||
});
|
});
|
||||||
|
$('#standalone').click(function () {
|
||||||
|
window.location = '{{ viz.standalone_endpoint | safe }}';
|
||||||
|
});
|
||||||
$("#viz_type").change(function() {$("#query").submit();});
|
$("#viz_type").change(function() {$("#query").submit();});
|
||||||
collapsed_fieldsets = get_collapsed_fieldsets();
|
collapsed_fieldsets = get_collapsed_fieldsets();
|
||||||
for(var i=0; i < collapsed_fieldsets.length; i++){
|
for(var i=0; i < collapsed_fieldsets.length; i++){
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
{% extends "panoramix/base.html" %}
|
{% extends "panoramix/base.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<div class="header">
|
||||||
<h1><i class='fa fa-star'></i> Featured Datasets </h1>
|
<h1><i class='fa fa-star'></i> Featured Datasets </h1>
|
||||||
|
</div>
|
||||||
<hr/>
|
<hr/>
|
||||||
<table class="table table-hover dataTable" id="dataset-table" style="display:None">
|
<table class="table table-hover dataTable" id="dataset-table" style="display:None">
|
||||||
<thead>
|
<thead>
|
||||||
|
@ -25,6 +27,7 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<hr/>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block head_css %}
|
{% block head_css %}
|
||||||
|
@ -42,6 +45,7 @@
|
||||||
"bPaginate": false,
|
"bPaginate": false,
|
||||||
"order": [[ 1, "asc" ]]
|
"order": [[ 1, "asc" ]]
|
||||||
});
|
});
|
||||||
|
$('#dataset-table_info').remove();
|
||||||
$('#dataset-table').show();
|
$('#dataset-table').show();
|
||||||
} );
|
} );
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<script src="/static/panoramix.js"></script>
|
||||||
|
|
||||||
|
{% block head_css %}
|
||||||
|
<script src="{{url_for('appbuilder.static',filename='js/jquery-latest.js')}}"></script>
|
||||||
|
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='panoramix.css') }}">
|
||||||
|
{% endblock %}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div
|
||||||
|
id="{{ viz.token }}"
|
||||||
|
class="viz slice {{ viz.viz_type }}"
|
||||||
|
data-slice="{{ viz.json_data }}"
|
||||||
|
style="height: 700px;">
|
||||||
|
<img src="{{ url_for("static", filename="img/loading.gif") }}" class="loading" alt="loading">
|
||||||
|
<div id="{{ viz.token }}_con" class="slice_container" style="height: 100%; width: 100%"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
var data = $('.slice').data('slice');
|
||||||
|
var slice = px.Slice(data);
|
||||||
|
slice.render();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% block tail %}{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1,37 +1,25 @@
|
||||||
{% import viz.template as viz_macros %}
|
|
||||||
|
|
||||||
{% if viz.form_data.get("json") == "true" %}
|
{% if viz.form_data.get("json") == "true" %}
|
||||||
{{ viz.get_json() }}
|
{{ viz.get_json() }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if viz.request.args.get("standalone") == "true" %}
|
{% if viz.request.args.get("standalone") == "true" %}
|
||||||
{% extends 'panoramix/viz_standalone.html' %}
|
{% extends 'panoramix/standalone.html' %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% extends 'panoramix/explore.html' %}
|
{% extends 'panoramix/explore.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
{% block viz_html %}
|
|
||||||
{{ viz_macros.viz_html(viz) }}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block head_css %}
|
{% block head_css %}
|
||||||
{{super()}}
|
{{super()}}
|
||||||
{% if viz.request.args.get("skip_libs") != "true" %}
|
|
||||||
{% for css in viz.css_files %}
|
{% for css in viz.css_files %}
|
||||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename=css) }}">
|
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename=css) }}">
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
|
||||||
{{ viz_macros.viz_css(viz) }}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
{% block tail %}
|
{% block tail %}
|
||||||
{{super()}}
|
{{super()}}
|
||||||
{% if viz.request.args.get("skip_libs") != "true" %}
|
|
||||||
{% for js in viz.js_files %}
|
{% for js in viz.js_files %}
|
||||||
<script src="{{ url_for('static', filename=js) }}"></script>
|
<script src="{{ url_for('static', filename=js) }}"></script>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{{ viz_macros.viz_js(viz) }}
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
{% macro viz_html(viz) %}
|
|
||||||
<div id="{{ viz.token }}" class="viz_bignumber" style="height: 100%;"></div>
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% macro viz_js(viz) %}
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% macro viz_css(viz) %}
|
|
||||||
{% endmacro %}
|
|
|
@ -1,9 +0,0 @@
|
||||||
{% macro viz_html(viz) %}
|
|
||||||
<div id="chart"></div>
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% macro viz_js(viz) %}
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% macro viz_css(viz) %}
|
|
||||||
{% endmacro %}
|
|
|
@ -1,9 +0,0 @@
|
||||||
{% macro viz_html(viz) %}
|
|
||||||
<div style="padding: 10px;overflow: auto; height: 100%;">{{ viz.rendered()|safe }}</div>
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% macro viz_js(viz) %}
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% macro viz_css(viz) %}
|
|
||||||
{% endmacro %}
|
|
|
@ -1,9 +0,0 @@
|
||||||
{% macro viz_html(viz) %}
|
|
||||||
<div class="chart with-3d-shadow with-transitions" style="height:100%; width: 100%"></div>
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% macro viz_js(viz) %}
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% macro viz_css(viz) %}
|
|
||||||
{% endmacro %}
|
|
|
@ -1,8 +0,0 @@
|
||||||
{% macro viz_html(viz) %}
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% macro viz_js(viz) %}
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% macro viz_css(viz) %}
|
|
||||||
{% endmacro %}
|
|
|
@ -1,10 +0,0 @@
|
||||||
{% macro viz_html(viz) %}
|
|
||||||
<div id="chart"></div>
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% macro viz_js(viz) %}
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% macro viz_css(viz) %}
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
{% if viz.request.args.get("skip_libs") != "true" %}
|
|
||||||
{% block head %}
|
|
||||||
<script src="{{url_for('appbuilder.static',filename='js/jquery-latest.js')}}"></script>
|
|
||||||
{% endblock %}
|
|
||||||
{% endif %}
|
|
||||||
{% block tail %}{% endblock %}
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
{% block viz_html %}{% endblock %}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,9 +0,0 @@
|
||||||
{% macro viz_html(viz) %}
|
|
||||||
<div id="chart"></div>
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% macro viz_js(viz) %}
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% macro viz_css(viz) %}
|
|
||||||
{% endmacro %}
|
|
|
@ -1,37 +0,0 @@
|
||||||
{% macro viz_html(viz) %}
|
|
||||||
{% if viz.request.args.get("async") == "true" %}
|
|
||||||
{% set df = viz.get_df() %}
|
|
||||||
<div class="table">
|
|
||||||
<table class="dataframe table table-striped table-bordered table-condensed table-hover">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
{% for col in df.columns if not col.endswith('__perc') %}
|
|
||||||
<th>{{ col }}</th>
|
|
||||||
{% endfor %}
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for row in df.to_dict(orient="records") %}
|
|
||||||
<tr>
|
|
||||||
{% for col in df.columns if not col.endswith('__perc') %}
|
|
||||||
{% if col + '__perc' in df.columns %}
|
|
||||||
<td style="background-image: linear-gradient(to right, lightgrey, lightgrey {{ row[col+'__perc'] }}%, rgba(0,0,0,0) {{ row[col+'__perc'] }}%">
|
|
||||||
{{ row[col] }}
|
|
||||||
</td>
|
|
||||||
{% else %}
|
|
||||||
<td>{{ row[col] }}</td>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% macro viz_js(viz) %}
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% macro viz_css(viz) %}
|
|
||||||
{% endmacro %}
|
|
|
@ -1,9 +0,0 @@
|
||||||
{% macro viz_html(viz) %}
|
|
||||||
<div id="{{ viz.token }}" style="height: 100%;"></div>
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% macro viz_js(viz) %}
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% macro viz_css(viz) %}
|
|
||||||
{% endmacro %}
|
|
|
@ -1,8 +0,0 @@
|
||||||
{% macro viz_html(viz) %}
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% macro viz_js(viz) %}
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% macro viz_css(viz) %}
|
|
||||||
{% endmacro %}
|
|
|
@ -212,3 +212,13 @@ def datetime_f(dttm):
|
||||||
elif now_iso[:4] == dttm[:4]:
|
elif now_iso[:4] == dttm[:4]:
|
||||||
dttm = dttm[5:]
|
dttm = dttm[5:]
|
||||||
return Markup("<nobr>{}</nobr>".format(dttm))
|
return Markup("<nobr>{}</nobr>".format(dttm))
|
||||||
|
|
||||||
|
|
||||||
|
def json_iso_dttm_ser(obj):
|
||||||
|
"""
|
||||||
|
json serializer that deals with dates
|
||||||
|
usage: json.dumps(object, default=utils.json_ser)
|
||||||
|
"""
|
||||||
|
if isinstance(obj, datetime):
|
||||||
|
obj = obj.isoformat()
|
||||||
|
return obj
|
||||||
|
|
|
@ -5,7 +5,7 @@ import uuid
|
||||||
|
|
||||||
from flask import flash, request, Markup
|
from flask import flash, request, Markup
|
||||||
from markdown import markdown
|
from markdown import markdown
|
||||||
from pandas.io.json import dumps
|
from pandas.io.json import dumps, to_json
|
||||||
from werkzeug.datastructures import ImmutableMultiDict
|
from werkzeug.datastructures import ImmutableMultiDict
|
||||||
from werkzeug.urls import Href
|
from werkzeug.urls import Href
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
@ -22,7 +22,6 @@ config = app.config
|
||||||
class BaseViz(object):
|
class BaseViz(object):
|
||||||
viz_type = None
|
viz_type = None
|
||||||
verbose_name = "Base Viz"
|
verbose_name = "Base Viz"
|
||||||
template = None
|
|
||||||
is_timeseries = False
|
is_timeseries = False
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
{
|
{
|
||||||
|
@ -157,6 +156,14 @@ class BaseViz(object):
|
||||||
eq = form_data.get("flt_eq_" + str(i))
|
eq = form_data.get("flt_eq_" + str(i))
|
||||||
if col and op and eq:
|
if col and op and eq:
|
||||||
filters.append((col, op, eq))
|
filters.append((col, op, eq))
|
||||||
|
|
||||||
|
# Extra filters (coming from dashboard)
|
||||||
|
extra_filters = form_data.get('extra_filters', [])
|
||||||
|
if extra_filters:
|
||||||
|
extra_filters = json.loads(extra_filters)
|
||||||
|
for slice_id, (col, vals) in extra_filters.items():
|
||||||
|
filters += [(col, 'in', ",".join(vals))]
|
||||||
|
|
||||||
return filters
|
return filters
|
||||||
|
|
||||||
def query_obj(self):
|
def query_obj(self):
|
||||||
|
@ -176,7 +183,7 @@ class BaseViz(object):
|
||||||
from_dttm = datetime.now() - (from_dttm-datetime.now())
|
from_dttm = datetime.now() - (from_dttm-datetime.now())
|
||||||
until = form_data.get("until", "now")
|
until = form_data.get("until", "now")
|
||||||
to_dttm = utils.parse_human_datetime(until)
|
to_dttm = utils.parse_human_datetime(until)
|
||||||
if from_dttm >= to_dttm:
|
if from_dttm > to_dttm:
|
||||||
flash("The date range doesn't seem right.", "danger")
|
flash("The date range doesn't seem right.", "danger")
|
||||||
from_dttm = to_dttm # Making them identical to not raise
|
from_dttm = to_dttm # Making them identical to not raise
|
||||||
|
|
||||||
|
@ -205,6 +212,9 @@ class BaseViz(object):
|
||||||
'data': json.loads(self.get_json_data()),
|
'data': json.loads(self.get_json_data()),
|
||||||
'query': self.query,
|
'query': self.query,
|
||||||
'form_data': self.form_data,
|
'form_data': self.form_data,
|
||||||
|
'json_endpoint': self.json_endpoint,
|
||||||
|
'csv_endpoint': self.csv_endpoint,
|
||||||
|
'standalone_endpoint': self.standalone_endpoint,
|
||||||
}
|
}
|
||||||
return json.dumps(payload)
|
return json.dumps(payload)
|
||||||
|
|
||||||
|
@ -223,19 +233,27 @@ class BaseViz(object):
|
||||||
def csv_endpoint(self):
|
def csv_endpoint(self):
|
||||||
return self.get_url(csv="true")
|
return self.get_url(csv="true")
|
||||||
|
|
||||||
def get_data_attribute(self):
|
@property
|
||||||
|
def standalone_endpoint(self):
|
||||||
|
return self.get_url(standalone="true")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def data(self):
|
||||||
content = {
|
content = {
|
||||||
'viz_name': self.viz_type,
|
'viz_name': self.viz_type,
|
||||||
'json_endpoint': self.json_endpoint,
|
'json_endpoint': self.json_endpoint,
|
||||||
'token': self.token,
|
'token': self.token,
|
||||||
'form_data': self.form_data,
|
'form_data': self.form_data,
|
||||||
}
|
}
|
||||||
return dumps(content)
|
return content
|
||||||
|
|
||||||
|
@property
|
||||||
|
def json_data(self):
|
||||||
|
return dumps(self.data)
|
||||||
|
|
||||||
class TableViz(BaseViz):
|
class TableViz(BaseViz):
|
||||||
viz_type = "table"
|
viz_type = "table"
|
||||||
verbose_name = "Table View"
|
verbose_name = "Table View"
|
||||||
template = 'panoramix/viz_table.html'
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
{
|
{
|
||||||
'label': None,
|
'label': None,
|
||||||
|
@ -293,16 +311,18 @@ class TableViz(BaseViz):
|
||||||
|
|
||||||
def get_json_data(self):
|
def get_json_data(self):
|
||||||
df = self.get_df()
|
df = self.get_df()
|
||||||
return dumps(dict(
|
return json.dumps(
|
||||||
|
dict(
|
||||||
records=df.to_dict(orient="records"),
|
records=df.to_dict(orient="records"),
|
||||||
columns=df.columns,
|
columns=list(df.columns),
|
||||||
))
|
),
|
||||||
|
default=utils.json_iso_dttm_ser,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class PivotTableViz(BaseViz):
|
class PivotTableViz(BaseViz):
|
||||||
viz_type = "pivot_table"
|
viz_type = "pivot_table"
|
||||||
verbose_name = "Pivot Table"
|
verbose_name = "Pivot Table"
|
||||||
template = 'panoramix/viz_pivot_table.html'
|
|
||||||
css_files = [
|
css_files = [
|
||||||
'lib/dataTables/dataTables.bootstrap.css',
|
'lib/dataTables/dataTables.bootstrap.css',
|
||||||
'widgets/viz_pivot_table.css']
|
'widgets/viz_pivot_table.css']
|
||||||
|
@ -373,7 +393,6 @@ class PivotTableViz(BaseViz):
|
||||||
class MarkupViz(BaseViz):
|
class MarkupViz(BaseViz):
|
||||||
viz_type = "markup"
|
viz_type = "markup"
|
||||||
verbose_name = "Markup Widget"
|
verbose_name = "Markup Widget"
|
||||||
template = 'panoramix/viz_markup.html'
|
|
||||||
js_files = ['widgets/viz_markup.js']
|
js_files = ['widgets/viz_markup.js']
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
{
|
{
|
||||||
|
@ -390,6 +409,9 @@ class MarkupViz(BaseViz):
|
||||||
elif markup_type == "html":
|
elif markup_type == "html":
|
||||||
return code
|
return code
|
||||||
|
|
||||||
|
def get_json_data(self):
|
||||||
|
return dumps(dict(html=self.rendered()))
|
||||||
|
|
||||||
|
|
||||||
class WordCloudViz(BaseViz):
|
class WordCloudViz(BaseViz):
|
||||||
"""
|
"""
|
||||||
|
@ -398,7 +420,6 @@ class WordCloudViz(BaseViz):
|
||||||
"""
|
"""
|
||||||
viz_type = "word_cloud"
|
viz_type = "word_cloud"
|
||||||
verbose_name = "Word Cloud"
|
verbose_name = "Word Cloud"
|
||||||
template = 'panoramix/viz_word_cloud.html'
|
|
||||||
is_timeseries = False
|
is_timeseries = False
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
{
|
{
|
||||||
|
@ -435,7 +456,6 @@ class WordCloudViz(BaseViz):
|
||||||
class NVD3Viz(BaseViz):
|
class NVD3Viz(BaseViz):
|
||||||
viz_type = None
|
viz_type = None
|
||||||
verbose_name = "Base NVD3 Viz"
|
verbose_name = "Base NVD3 Viz"
|
||||||
template = 'panoramix/viz_nvd3.html'
|
|
||||||
is_timeseries = False
|
is_timeseries = False
|
||||||
js_files = [
|
js_files = [
|
||||||
'lib/d3.min.js',
|
'lib/d3.min.js',
|
||||||
|
@ -458,8 +478,8 @@ class BubbleViz(NVD3Viz):
|
||||||
'fields': (
|
'fields': (
|
||||||
('since', 'until'),
|
('since', 'until'),
|
||||||
('series', 'entity'),
|
('series', 'entity'),
|
||||||
('x', 'y'),
|
'x', 'y', 'size',
|
||||||
('size', 'limit'),
|
'limit',
|
||||||
('x_log_scale', 'y_log_scale'),
|
('x_log_scale', 'y_log_scale'),
|
||||||
('show_legend', None),
|
('show_legend', None),
|
||||||
)
|
)
|
||||||
|
@ -517,7 +537,6 @@ class BubbleViz(NVD3Viz):
|
||||||
class BigNumberViz(BaseViz):
|
class BigNumberViz(BaseViz):
|
||||||
viz_type = "big_number"
|
viz_type = "big_number"
|
||||||
verbose_name = "Big Number"
|
verbose_name = "Big Number"
|
||||||
template = 'panoramix/viz_bignumber.html'
|
|
||||||
is_timeseries = True
|
is_timeseries = True
|
||||||
js_files = [
|
js_files = [
|
||||||
'lib/d3.min.js',
|
'lib/d3.min.js',
|
||||||
|
@ -850,7 +869,6 @@ class SunburstViz(BaseViz):
|
||||||
viz_type = "sunburst"
|
viz_type = "sunburst"
|
||||||
verbose_name = "Sunburst"
|
verbose_name = "Sunburst"
|
||||||
is_timeseries = False
|
is_timeseries = False
|
||||||
template = 'panoramix/viz_sunburst.html'
|
|
||||||
js_files = [
|
js_files = [
|
||||||
'lib/d3.min.js',
|
'lib/d3.min.js',
|
||||||
'widgets/viz_sunburst.js']
|
'widgets/viz_sunburst.js']
|
||||||
|
@ -917,7 +935,6 @@ class SankeyViz(BaseViz):
|
||||||
viz_type = "sankey"
|
viz_type = "sankey"
|
||||||
verbose_name = "Sankey"
|
verbose_name = "Sankey"
|
||||||
is_timeseries = False
|
is_timeseries = False
|
||||||
template = 'panoramix/viz_sankey.html'
|
|
||||||
js_files = [
|
js_files = [
|
||||||
'lib/d3.min.js',
|
'lib/d3.min.js',
|
||||||
'lib/d3-sankey.js',
|
'lib/d3-sankey.js',
|
||||||
|
@ -934,10 +951,17 @@ class SankeyViz(BaseViz):
|
||||||
'row_limit',
|
'row_limit',
|
||||||
)
|
)
|
||||||
},)
|
},)
|
||||||
form_overrides = {}
|
form_overrides = {
|
||||||
|
'groupby': {
|
||||||
|
'label': 'Source / Target',
|
||||||
|
'description': "Choose a source and a target",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
def query_obj(self):
|
def query_obj(self):
|
||||||
qry = super(SankeyViz, self).query_obj()
|
qry = super(SankeyViz, self).query_obj()
|
||||||
|
if len(qry['groupby']) != 2:
|
||||||
|
raise Exception("Pick exactly 2 columns as [Source / Target]")
|
||||||
qry['metrics'] = [
|
qry['metrics'] = [
|
||||||
self.form_data['metric']]
|
self.form_data['metric']]
|
||||||
return qry
|
return qry
|
||||||
|
@ -953,7 +977,6 @@ class DirectedForceViz(BaseViz):
|
||||||
viz_type = "directed_force"
|
viz_type = "directed_force"
|
||||||
verbose_name = "Directed Force Layout"
|
verbose_name = "Directed Force Layout"
|
||||||
is_timeseries = False
|
is_timeseries = False
|
||||||
template = 'panoramix/viz_directed_force.html'
|
|
||||||
js_files = [
|
js_files = [
|
||||||
'lib/d3.min.js',
|
'lib/d3.min.js',
|
||||||
'widgets/viz_directed_force.js']
|
'widgets/viz_directed_force.js']
|
||||||
|
@ -1000,7 +1023,6 @@ class WorldMapViz(BaseViz):
|
||||||
viz_type = "world_map"
|
viz_type = "world_map"
|
||||||
verbose_name = "World Map"
|
verbose_name = "World Map"
|
||||||
is_timeseries = False
|
is_timeseries = False
|
||||||
template = 'panoramix/viz_world_map.html'
|
|
||||||
js_files = [
|
js_files = [
|
||||||
'lib/d3.min.js',
|
'lib/d3.min.js',
|
||||||
'lib/topojson.min.js',
|
'lib/topojson.min.js',
|
||||||
|
|
|
@ -2,4 +2,4 @@
|
||||||
rm /tmp/panoramix_unittests.db
|
rm /tmp/panoramix_unittests.db
|
||||||
export PANORAMIX_CONFIG=tests.panoramix_test_config
|
export PANORAMIX_CONFIG=tests.panoramix_test_config
|
||||||
panoramix/bin/panoramix db upgrade
|
panoramix/bin/panoramix db upgrade
|
||||||
nosetests tests/core_tests.py --with-coverage --cover-package=panoramix
|
nosetests tests/core_tests.py --with-coverage --cover-package=panoramix -v
|
||||||
|
|
Loading…
Reference in New Issue