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
|
||||
|
||||
## 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
|
||||
* [sql] make "Test Connection" test further
|
||||
* [druid] Allow for post aggregations (ratios!)
|
||||
|
|
|
@ -1,19 +1,12 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import csv
|
||||
from datetime import datetime
|
||||
import gzip
|
||||
import json
|
||||
import os
|
||||
from subprocess import Popen
|
||||
|
||||
from flask.ext.script import Manager
|
||||
from panoramix import app
|
||||
from flask.ext.migrate import MigrateCommand
|
||||
from panoramix import db
|
||||
from flask.ext.appbuilder import Base
|
||||
from sqlalchemy import Column, Integer, String, Table, DateTime
|
||||
from panoramix import models, utils
|
||||
from panoramix import data, utils
|
||||
|
||||
config = app.config
|
||||
|
||||
|
@ -60,314 +53,10 @@ def load_examples(sample):
|
|||
"""Loads a set of Slices and Dashboards and a supporting dataset """
|
||||
print("Loading examples into {}".format(db))
|
||||
|
||||
|
||||
BirthNames = Table(
|
||||
"birth_names", Base.metadata,
|
||||
Column("id", Integer, primary_key=True),
|
||||
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()
|
||||
print("Loading [World Bank's Health Nutrition and Population Stats]")
|
||||
data.load_world_bank_health_n_pop()
|
||||
print("Loading [Birth names]")
|
||||
data.load_birth_names()
|
||||
|
||||
|
||||
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,7 +32,8 @@ class SelectMultipleSortableField(SelectMultipleField):
|
|||
d[value] = (value, label, selected)
|
||||
if self.data:
|
||||
for value in self.data:
|
||||
yield d.pop(value)
|
||||
if value:
|
||||
yield d.pop(value)
|
||||
while d:
|
||||
yield d.pop(d.keys()[0])
|
||||
|
||||
|
@ -402,6 +403,7 @@ class FormFactory(object):
|
|||
css_classes = field_css_classes
|
||||
standalone = HiddenField()
|
||||
async = HiddenField()
|
||||
extra_filters = HiddenField()
|
||||
json = HiddenField()
|
||||
slice_id = 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 flask import flash
|
||||
from flask.ext.appbuilder import Model
|
||||
from flask.ext.appbuilder.models.mixins import AuditMixin
|
||||
import pandas as pd
|
||||
from pandas import read_sql_query
|
||||
from pydruid import client
|
||||
from pydruid.utils.filters import Dimension, Filter
|
||||
|
@ -11,19 +20,11 @@ from sqlalchemy import (
|
|||
Column, Integer, String, ForeignKey, Text, Boolean, DateTime,
|
||||
Table, create_engine, MetaData, desc, select, and_, func)
|
||||
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_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.viz import viz_types
|
||||
|
@ -85,6 +86,16 @@ class Slice(Model, AuditMixinNullable):
|
|||
def datasource_id(self):
|
||||
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
|
||||
def slice_url(self):
|
||||
try:
|
||||
|
@ -409,9 +420,7 @@ class SqlaTable(Model, Queryable, AuditMixinNullable):
|
|||
groupby_exprs = []
|
||||
|
||||
if groupby:
|
||||
select_exprs = [literal_column(s) for s in groupby]
|
||||
select_exprs = []
|
||||
groupby_exprs = []
|
||||
inner_select_exprs = []
|
||||
inner_groupby_exprs = []
|
||||
for s in groupby:
|
||||
|
@ -421,8 +430,8 @@ class SqlaTable(Model, Queryable, AuditMixinNullable):
|
|||
outer = ColumnClause(expr, is_literal=True).label(s)
|
||||
inner = ColumnClause(expr, is_literal=True).label('__' + s)
|
||||
else:
|
||||
outer = literal_column(s).label(s)
|
||||
inner = literal_column(s).label('__' + s)
|
||||
outer = column(s).label(s)
|
||||
inner = column(s).label('__' + s)
|
||||
|
||||
groupby_exprs.append(outer)
|
||||
select_exprs.append(outer)
|
||||
|
@ -462,7 +471,7 @@ class SqlaTable(Model, Queryable, AuditMixinNullable):
|
|||
cond = ColumnClause(
|
||||
col_obj.expression, is_literal=True).in_(values)
|
||||
else:
|
||||
cond = literal_column(col).in_(values)
|
||||
cond = column(col).in_(values)
|
||||
if op == 'not in':
|
||||
cond = ~cond
|
||||
where_clause_and.append(cond)
|
||||
|
@ -486,7 +495,7 @@ class SqlaTable(Model, Queryable, AuditMixinNullable):
|
|||
on_clause = []
|
||||
for i, gb in enumerate(groupby):
|
||||
on_clause.append(
|
||||
groupby_exprs[i] == literal_column("__" + gb))
|
||||
groupby_exprs[i] == column("__" + gb))
|
||||
|
||||
from_clause = from_clause.join(subq.alias(), and_(*on_clause))
|
||||
|
||||
|
@ -499,7 +508,6 @@ class SqlaTable(Model, Queryable, AuditMixinNullable):
|
|||
con=engine
|
||||
)
|
||||
sql = sqlparse.format(sql, reindent=True)
|
||||
print(sql)
|
||||
return QueryResult(
|
||||
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():
|
||||
any_date_col = col.name
|
||||
|
||||
quoted = "{}".format(
|
||||
column(dbcol.column_name).compile(dialect=db.engine.dialect))
|
||||
if dbcol.sum:
|
||||
metrics.append(M(
|
||||
metric_name='sum__' + dbcol.column_name,
|
||||
verbose_name='sum__' + dbcol.column_name,
|
||||
metric_type='sum',
|
||||
expression="SUM({})".format(dbcol.column_name)
|
||||
expression="SUM({})".format(quoted)
|
||||
))
|
||||
if dbcol.max:
|
||||
metrics.append(M(
|
||||
metric_name='max__' + dbcol.column_name,
|
||||
verbose_name='max__' + dbcol.column_name,
|
||||
metric_type='max',
|
||||
expression="MAX({})".format(dbcol.column_name)
|
||||
expression="MAX({})".format(quoted)
|
||||
))
|
||||
if dbcol.min:
|
||||
metrics.append(M(
|
||||
metric_name='min__' + dbcol.column_name,
|
||||
verbose_name='min__' + dbcol.column_name,
|
||||
metric_type='min',
|
||||
expression="MIN({})".format(dbcol.column_name)
|
||||
expression="MIN({})".format(quoted)
|
||||
))
|
||||
if dbcol.count_distinct:
|
||||
metrics.append(M(
|
||||
metric_name='count_distinct__' + dbcol.column_name,
|
||||
verbose_name='count_distinct__' + dbcol.column_name,
|
||||
metric_type='count_distinct',
|
||||
expression="COUNT(DISTINCT {})".format(dbcol.column_name)
|
||||
expression="COUNT(DISTINCT {})".format(quoted)
|
||||
))
|
||||
dbcol.type = datatype
|
||||
db.session.merge(self)
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
html>body{
|
||||
margin: 0px; !important
|
||||
}
|
||||
|
||||
.slice_container {
|
||||
height: 100%;
|
||||
}
|
||||
.container-fluid {
|
||||
text-align: left;
|
||||
}
|
||||
|
@ -132,8 +136,8 @@ legend {
|
|||
.datasource .tooltip-inner {
|
||||
max-width: 350px;
|
||||
}
|
||||
.datasource img.loading {
|
||||
width: 30px;
|
||||
img.loading {
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.dashboard a i {
|
||||
|
@ -181,7 +185,7 @@ legend {
|
|||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.dashboard table.widget_header {
|
||||
.dashboard table.slice_header {
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
}
|
||||
|
@ -192,7 +196,7 @@ legend {
|
|||
.dashboard li.widget.pie,
|
||||
.dashboard li.widget.dist_bar,
|
||||
.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 {
|
||||
z-index: 888; /* this lets tool tips go on top of other slices */
|
||||
|
|
|
@ -1,249 +1,339 @@
|
|||
var timer;
|
||||
var px = (function() {
|
||||
|
||||
var visualizations = [];
|
||||
var visualizations = {};
|
||||
var dashboard = undefined;
|
||||
|
||||
function registerWidget(name, initializer) {
|
||||
visualizations[name] = initializer;
|
||||
}
|
||||
|
||||
function makeNullWidget() {
|
||||
return {
|
||||
render: function() {},
|
||||
resize: function() {},
|
||||
};
|
||||
}
|
||||
|
||||
function initializeWidget(data) {
|
||||
var Slice = function(data, dashboard){
|
||||
var timer;
|
||||
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 initializer = visualizations[name];
|
||||
var user_defined_widget = initializer ? initializer(data) : makeNullWidget();
|
||||
var dttm = 0;
|
||||
var timer;
|
||||
var stopwatch = function () {
|
||||
dttm += 10;
|
||||
$('#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) {
|
||||
clearInterval(timer);
|
||||
token.find("img.loading").hide();
|
||||
token.find("img.loading").hide()
|
||||
container.show();
|
||||
if(data !== undefined)
|
||||
$("#query_container").html(data.query);
|
||||
$('#timer').removeClass('btn-warning');
|
||||
$('span.query').removeClass('disabled');
|
||||
$('#timer').addClass('btn-success');
|
||||
$('span.query').removeClass('disabled');
|
||||
},
|
||||
error: function (msg) {
|
||||
clearInterval(timer);
|
||||
token.find("img.loading").hide();
|
||||
var err = '<div class="alert alert-danger">' + msg + '</div>';
|
||||
token.html(err);
|
||||
container.html(err);
|
||||
container.show();
|
||||
$('#timer').removeClass('btn-warning');
|
||||
$('span.query').removeClass('disabled');
|
||||
$('#timer').addClass('btn-danger');
|
||||
}
|
||||
};
|
||||
widget = {
|
||||
},
|
||||
data: data,
|
||||
container: container,
|
||||
container_id: container_id,
|
||||
selector: selector,
|
||||
render: function() {
|
||||
token.find("img.loading").show();
|
||||
container.hide();
|
||||
container.html('');
|
||||
dttm = 0;
|
||||
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() {
|
||||
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) {
|
||||
name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
|
||||
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
|
||||
results = regex.exec(location.search);
|
||||
return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
|
||||
}
|
||||
|
||||
$(".select2").select2({dropdownAutoWidth : true});
|
||||
$(".select2Sortable").select2();
|
||||
$(".select2Sortable").select2Sortable();
|
||||
$("form").show();
|
||||
$('[data-toggle="tooltip"]').tooltip({container: 'body'});
|
||||
|
||||
function set_filters(){
|
||||
for (var i = 1; i < 10; i++){
|
||||
var eq = getParam("flt_eq_" + i);
|
||||
if (eq != ''){
|
||||
add_filter(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
set_filters();
|
||||
|
||||
function add_filter(i) {
|
||||
cp = $("#flt0").clone();
|
||||
$(cp).appendTo("#filters");
|
||||
$(cp).show();
|
||||
if (i != undefined){
|
||||
$(cp).find("#flt_eq_0").val(getParam("flt_eq_" + i));
|
||||
$(cp).find("#flt_op_0").val(getParam("flt_op_" + i));
|
||||
$(cp).find("#flt_col_0").val(getParam("flt_col_" + i));
|
||||
}
|
||||
$(cp).find('select').select2();
|
||||
$(cp).find('.remove').click(function() {
|
||||
$(this).parent().parent().remove();
|
||||
});
|
||||
}
|
||||
$(".select2").select2({dropdownAutoWidth : true});
|
||||
$(".select2Sortable").select2();
|
||||
$(".select2Sortable").select2Sortable();
|
||||
$("form").show();
|
||||
$('[data-toggle="tooltip"]').tooltip({container: 'body'});
|
||||
|
||||
function druidify(){
|
||||
var i = 1;
|
||||
// Assigning the right id to form elements in filters
|
||||
$("#filters > div").each(function() {
|
||||
$(this).attr("id", function() {return "flt_" + i;})
|
||||
$(this).find("#flt_col_0")
|
||||
.attr("id", function() {return "flt_col_" + i;})
|
||||
.attr("name", function() {return "flt_col_" + i;});
|
||||
$(this).find("#flt_op_0")
|
||||
.attr("id", function() {return "flt_op_" + i;})
|
||||
.attr("name", function() {return "flt_op_" + i;});
|
||||
$(this).find("#flt_eq_0")
|
||||
.attr("id", function() {return "flt_eq_" + i;})
|
||||
.attr("name", function() {return "flt_eq_" + i;});
|
||||
i++;
|
||||
});
|
||||
$("#query").submit();
|
||||
}
|
||||
|
||||
$("#plus").click(add_filter);
|
||||
$("#btn_save").click(function () {
|
||||
var slice_name = prompt("Name your slice!");
|
||||
if (slice_name != "" && slice_name != null) {
|
||||
$("#slice_name").val(slice_name);
|
||||
$("#action").val("save");
|
||||
druidify();
|
||||
}
|
||||
});
|
||||
$("#btn_overwrite").click(function () {
|
||||
var flag = confirm("Overwrite slice [" + $("#slice_name").val() + "] !?");
|
||||
if (flag) {
|
||||
$("#action").val("overwrite");
|
||||
druidify();
|
||||
}
|
||||
});
|
||||
add_filter();
|
||||
$(".druidify").click(druidify);
|
||||
|
||||
function create_choices(term, data) {
|
||||
var filtered = $(data).filter(function() {
|
||||
return this.text.localeCompare(term) === 0;
|
||||
});
|
||||
if (filtered.length === 0) {
|
||||
return {id: term, text: term};
|
||||
}
|
||||
}
|
||||
function initSelectionToValue(element, callback) {
|
||||
callback({id: element.val(), text: element.val()});
|
||||
}
|
||||
function list_data(arr) {
|
||||
var obj = [];
|
||||
for (var i=0; i<arr.length; i++){
|
||||
obj.push({id: arr[i], text: arr[i]});
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
$(".select2_freeform").each(function(){
|
||||
parent = $(this).parent();
|
||||
var name = $(this).attr('name');
|
||||
var l = [];
|
||||
var selected = '';
|
||||
for(var i=0; i<this.options.length; i++) {
|
||||
l.push({id: this.options[i].value, text: this.options[i].text});
|
||||
if(this.options[i].selected){
|
||||
selected = this.options[i].value;
|
||||
function set_filters(){
|
||||
for (var i = 1; i < 10; i++){
|
||||
var eq = getParam("flt_eq_" + i);
|
||||
if (eq != ''){
|
||||
add_filter(i);
|
||||
}
|
||||
}
|
||||
obj = parent.append(
|
||||
'<input class="' + $(this).attr('class') + '" name="'+ name +'" type="text" value="' + selected + '">');
|
||||
$("input[name='" + name +"']")
|
||||
.select2({
|
||||
createSearchChoice: create_choices,
|
||||
initSelection: initSelectionToValue,
|
||||
multiple: false,
|
||||
data: l,
|
||||
});
|
||||
$(this).remove();
|
||||
});
|
||||
}
|
||||
|
||||
function initializeDashboardView(dashboard_id) {
|
||||
var gridster = $(".gridster ul").gridster({
|
||||
widget_margins: [5, 5],
|
||||
widget_base_dimensions: [100, 100],
|
||||
draggable: {
|
||||
handle: '.drag',
|
||||
},
|
||||
resize: {
|
||||
enabled: true,
|
||||
stop: function(e, ui, element) {
|
||||
var widget = $(element).data('widget');
|
||||
widget.resize();
|
||||
}
|
||||
},
|
||||
serialize_params: function(_w, wgd) {
|
||||
return {
|
||||
slice_id: $(_w).attr('slice_id'),
|
||||
col: wgd.col,
|
||||
row: wgd.row,
|
||||
size_x: wgd.size_x,
|
||||
size_y: wgd.size_y
|
||||
};
|
||||
},
|
||||
}).data('gridster');
|
||||
$("div.gridster").css('visibility', 'visible');
|
||||
$("#savedash").click(function() {
|
||||
var data = {
|
||||
positions: gridster.serialize(),
|
||||
css: $("#dash_css").val()
|
||||
};
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: '/panoramix/save_dash/' + dashboard_id + '/',
|
||||
data: {'data': JSON.stringify(data)},
|
||||
success: function() {alert("Saved!")},
|
||||
error: function() {alert("Error :(")},
|
||||
}
|
||||
set_filters();
|
||||
|
||||
function add_filter(i) {
|
||||
cp = $("#flt0").clone();
|
||||
$(cp).appendTo("#filters");
|
||||
$(cp).show();
|
||||
if (i != undefined){
|
||||
$(cp).find("#flt_eq_0").val(getParam("flt_eq_" + i));
|
||||
$(cp).find("#flt_op_0").val(getParam("flt_op_" + i));
|
||||
$(cp).find("#flt_col_0").val(getParam("flt_col_" + i));
|
||||
}
|
||||
$(cp).find('select').select2();
|
||||
$(cp).find('.remove').click(function() {
|
||||
$(this).parent().parent().remove();
|
||||
});
|
||||
}
|
||||
function prepForm(){
|
||||
var i = 1;
|
||||
// Assigning the right id to form elements in filters
|
||||
$("#filters > div").each(function() {
|
||||
$(this).attr("id", function() {return "flt_" + i;})
|
||||
$(this).find("#flt_col_0")
|
||||
.attr("id", function() {return "flt_col_" + i;})
|
||||
.attr("name", function() {return "flt_col_" + i;});
|
||||
$(this).find("#flt_op_0")
|
||||
.attr("id", function() {return "flt_op_" + i;})
|
||||
.attr("name", function() {return "flt_op_" + i;});
|
||||
$(this).find("#flt_eq_0")
|
||||
.attr("id", function() {return "flt_eq_" + i;})
|
||||
.attr("name", function() {return "flt_eq_" + i;});
|
||||
i++;
|
||||
});
|
||||
}
|
||||
|
||||
function druidify(){
|
||||
prepForm();
|
||||
slice.render();
|
||||
}
|
||||
|
||||
$("#plus").click(add_filter);
|
||||
$("#btn_save").click(function () {
|
||||
var slice_name = prompt("Name your slice!");
|
||||
if (slice_name != "" && slice_name != null) {
|
||||
$("#slice_name").val(slice_name);
|
||||
prepForm();
|
||||
$("#action").val("save");
|
||||
$("#query").submit();
|
||||
}
|
||||
});
|
||||
});
|
||||
$("a.closewidget").click(function() {
|
||||
var li = $(this).parents("li");
|
||||
gridster.remove_widget(li);
|
||||
});
|
||||
$("table.widget_header").mouseover(function() {
|
||||
$(this).find("td.icons nobr").show();
|
||||
});
|
||||
$("table.widget_header").mouseout(function() {
|
||||
$(this).find("td.icons nobr").hide();
|
||||
});
|
||||
$("#dash_css").on("keyup", function(){
|
||||
css = $(this).val();
|
||||
$("#user_style").html(css);
|
||||
});
|
||||
$('li.widget').each(function() { /* this sets the z-index for left side boxes higher. */
|
||||
current_row = $(this).attr('data-col');
|
||||
$( this ).css('z-index', 100 - current_row);
|
||||
});
|
||||
$("div.chart").each(function() { /* this makes the whole chart fit within the dashboard div */
|
||||
$(this).css('height', '95%');
|
||||
});
|
||||
}
|
||||
$("#btn_overwrite").click(function () {
|
||||
var flag = confirm("Overwrite slice [" + $("#slice_name").val() + "] !?");
|
||||
if (flag) {
|
||||
$("#action").val("overwrite");
|
||||
prepForm();
|
||||
$("#query").submit();
|
||||
}
|
||||
});
|
||||
add_filter();
|
||||
$(".druidify").click(druidify);
|
||||
|
||||
function create_choices(term, data) {
|
||||
var filtered = $(data).filter(function() {
|
||||
return this.text.localeCompare(term) === 0;
|
||||
});
|
||||
if (filtered.length === 0) {
|
||||
return {id: term, text: term};
|
||||
}
|
||||
}
|
||||
function initSelectionToValue(element, callback) {
|
||||
callback({id: element.val(), text: element.val()});
|
||||
}
|
||||
function list_data(arr) {
|
||||
var obj = [];
|
||||
for (var i=0; i<arr.length; i++){
|
||||
obj.push({id: arr[i], text: arr[i]});
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
$(".select2_freeform").each(function(){
|
||||
parent = $(this).parent();
|
||||
var name = $(this).attr('name');
|
||||
var l = [];
|
||||
var selected = '';
|
||||
for(var i=0; i<this.options.length; i++) {
|
||||
l.push({id: this.options[i].value, text: this.options[i].text});
|
||||
if(this.options[i].selected){
|
||||
selected = this.options[i].value;
|
||||
}
|
||||
}
|
||||
obj = parent.append(
|
||||
'<input class="' + $(this).attr('class') + '" name="'+ name +'" type="text" value="' + selected + '">');
|
||||
$("input[name='" + name +"']")
|
||||
.select2({
|
||||
createSearchChoice: create_choices,
|
||||
initSelection: initSelectionToValue,
|
||||
multiple: false,
|
||||
data: l,
|
||||
});
|
||||
$(this).remove();
|
||||
});
|
||||
}
|
||||
|
||||
function initDashboardView() {
|
||||
var gridster = $(".gridster ul").gridster({
|
||||
widget_margins: [5, 5],
|
||||
widget_base_dimensions: [100, 100],
|
||||
draggable: {
|
||||
handle: '.drag',
|
||||
},
|
||||
resize: {
|
||||
enabled: true,
|
||||
stop: function(e, ui, element) {
|
||||
var slice_data = $(element).data('slice');
|
||||
dashboard.getSlice(slice_data.slice_id).resize();
|
||||
}
|
||||
},
|
||||
serialize_params: function(_w, wgd) {
|
||||
return {
|
||||
slice_id: $(_w).attr('slice_id'),
|
||||
col: wgd.col,
|
||||
row: wgd.row,
|
||||
size_x: wgd.size_x,
|
||||
size_y: wgd.size_y
|
||||
};
|
||||
},
|
||||
}).data('gridster');
|
||||
$("div.gridster").css('visibility', 'visible');
|
||||
$("#savedash").click(function() {
|
||||
var data = {
|
||||
positions: gridster.serialize(),
|
||||
css: $("#dash_css").val()
|
||||
};
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: '/panoramix/save_dash/' + dashboard.id + '/',
|
||||
data: {'data': JSON.stringify(data)},
|
||||
success: function() {alert("Saved!")},
|
||||
error: function() {alert("Error :(")},
|
||||
});
|
||||
});
|
||||
$("a.closeslice").click(function() {
|
||||
var li = $(this).parents("li");
|
||||
gridster.remove_widget(li);
|
||||
});
|
||||
$("table.slice_header").mouseover(function() {
|
||||
$(this).find("td.icons nobr").show();
|
||||
});
|
||||
$("table.slice_header").mouseout(function() {
|
||||
$(this).find("td.icons nobr").hide();
|
||||
});
|
||||
$("#dash_css").on("keyup", function(){
|
||||
css = $(this).val();
|
||||
$("#user_style").html(css);
|
||||
});
|
||||
|
||||
// this sets the z-index for left side boxes higher
|
||||
$('li.slice').each(function() {
|
||||
current_row = $(this).attr('data-col');
|
||||
$( this ).css('z-index', 100 - current_row);
|
||||
});
|
||||
|
||||
// this makes the whole chart fit within the dashboard div
|
||||
$("div.chart").each(function() {
|
||||
$(this).css('height', '95%');
|
||||
});
|
||||
}
|
||||
|
||||
// Export public functions
|
||||
return {
|
||||
registerWidget: registerWidget,
|
||||
initializeWidget: initializeWidget,
|
||||
initializeDatasourceView: initializeDatasourceView,
|
||||
initializeDashboardView: initializeDashboardView,
|
||||
registerViz: registerViz,
|
||||
Slice: Slice,
|
||||
Dashboard: Dashboard,
|
||||
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'];
|
||||
var json_callback = data_attribute['json_endpoint'];
|
||||
var div = d3.select('#' + token_name);
|
||||
|
||||
function render(ctrl) {
|
||||
d3.json(json_callback, function(error, payload){
|
||||
function render() {
|
||||
d3.json(slice.jsonEndpoint(), function(error, payload){
|
||||
//Define the percentage bounds that define color from red to green
|
||||
div.html("");
|
||||
if (error != null){
|
||||
ctrl.error(error.responseText);
|
||||
slice.error(error.responseText);
|
||||
return '';
|
||||
}
|
||||
json = payload.data;
|
||||
|
@ -19,9 +16,8 @@ px.registerWidget('big_number', function(data_attribute) {
|
|||
|
||||
var f = d3.format('.3s');
|
||||
var fp = d3.format('+.1%');
|
||||
var xy = div.node().getBoundingClientRect();
|
||||
var width = xy.width;
|
||||
var height = xy.height - 30;
|
||||
var width = slice.container.width();
|
||||
var height = slice.container.height() - 30;
|
||||
var svg = div.append('svg');
|
||||
svg.attr("width", width);
|
||||
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.axis').transition().duration(500).attr('opacity', 0);
|
||||
});
|
||||
ctrl.done(payload);
|
||||
slice.done(payload);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -3,9 +3,6 @@
|
|||
stroke: #000;
|
||||
stroke-width: 1.5px;
|
||||
}
|
||||
.directed_force #chart {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.directed_force circle {
|
||||
fill: #ccc;
|
||||
|
|
|
@ -2,25 +2,23 @@
|
|||
Modified from http://bl.ocks.org/d3noob/5141278
|
||||
*/
|
||||
|
||||
function viz_directed_force(data_attribute) {
|
||||
var token = d3.select('#' + data_attribute.token);
|
||||
var xy = token.select('#chart').node().getBoundingClientRect();
|
||||
var width = xy.width;
|
||||
var height = xy.height - 25;
|
||||
var radius = Math.min(width, height) / 2;
|
||||
var link_length = data_attribute.form_data['link_length'];
|
||||
function viz_directed_force(slice) {
|
||||
var width = slice.container.width();
|
||||
var height = slice.container.height() - 25;
|
||||
var link_length = slice.data.form_data['link_length'];
|
||||
var div = d3.select(slice.selector);
|
||||
if (link_length === undefined){
|
||||
link_length = 200;
|
||||
}
|
||||
var charge = data_attribute.form_data['charge'];
|
||||
var charge = slice.data.form_data['charge'];
|
||||
if (charge === undefined){
|
||||
charge = -500;
|
||||
}
|
||||
var render = function(ctrl) {
|
||||
d3.json(data_attribute.json_endpoint, function(error, json) {
|
||||
var render = function() {
|
||||
d3.json(slice.jsonEndpoint(), function(error, json) {
|
||||
|
||||
if (error != null){
|
||||
ctrl.error(error.responseText);
|
||||
slice.error(error.responseText);
|
||||
return '';
|
||||
}
|
||||
links = json.data;
|
||||
|
@ -59,7 +57,7 @@ function viz_directed_force(data_attribute) {
|
|||
.on("tick", tick)
|
||||
.start();
|
||||
|
||||
var svg = token.select("#chart").append("svg")
|
||||
var svg = div.append("svg")
|
||||
.attr("width", width)
|
||||
.attr("height", height);
|
||||
|
||||
|
@ -150,7 +148,7 @@ function viz_directed_force(data_attribute) {
|
|||
.attr("transform", function(d) {
|
||||
return "translate(" + d.x + "," + d.y + ")"; });
|
||||
}
|
||||
ctrl.done(json);
|
||||
slice.done(json);
|
||||
});
|
||||
}
|
||||
return {
|
||||
|
@ -158,4 +156,4 @@ function viz_directed_force(data_attribute) {
|
|||
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')
|
||||
ctrl.done();
|
||||
}
|
||||
$.getJSON(slice.jsonEndpoint(), function(payload) {
|
||||
slice.container.html(payload.data.html);
|
||||
slice.done();
|
||||
})
|
||||
.fail(function(xhr) {
|
||||
slice.error(xhr.responseText);
|
||||
});
|
||||
};
|
||||
return {
|
||||
render: refresh,
|
||||
resize: refresh,
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
function viz_nvd3(data_attribute) {
|
||||
var token_name = data_attribute['token'];
|
||||
var token = d3.select('#' + token_name);
|
||||
var json_callback = data_attribute['json_endpoint'];
|
||||
function viz_nvd3(slice) {
|
||||
var chart = undefined;
|
||||
var data = {};
|
||||
|
||||
|
@ -26,15 +23,13 @@ function viz_nvd3(data_attribute) {
|
|||
"#FF5A5F", "#007A87", "#7B0051", "#00D1C1", "#8CE071", "#FFB400",
|
||||
"#FFAA91", "#B4A76C", "#9CA299", "#565A5C"
|
||||
];
|
||||
var jtoken = $('#' + token_name);
|
||||
var chart_div = $('#' + token_name).find("div.chart");
|
||||
|
||||
var refresh = function(ctrl) {
|
||||
chart_div.hide();
|
||||
$.getJSON(json_callback, function(payload) {
|
||||
var refresh = function() {
|
||||
$.getJSON(slice.jsonEndpoint(), function(payload) {
|
||||
var data = payload.data;
|
||||
var viz = payload;
|
||||
var viz_type = viz.form_data.viz_type;
|
||||
var fd = viz.form_data;
|
||||
var f = d3.format('.4s');
|
||||
nv.addGraph(function() {
|
||||
if (viz_type === 'line') {
|
||||
if (viz.form_data.show_brush) {
|
||||
|
@ -113,10 +108,25 @@ function viz_nvd3(data_attribute) {
|
|||
chart.yAxis.tickFormat(d3.format('.3p'));
|
||||
|
||||
} else if (viz_type === 'bubble') {
|
||||
var row = function(col1, col2){
|
||||
return "<tr><td>" + col1 + "</td><td>" + col2 + "</td></r>"
|
||||
}
|
||||
chart = nv.models.scatterChart();
|
||||
chart.showDistX(true);
|
||||
chart.showDistY(true);
|
||||
chart.xAxis.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]);
|
||||
|
||||
} else if (viz_type === 'area') {
|
||||
|
@ -130,7 +140,7 @@ function viz_nvd3(data_attribute) {
|
|||
}
|
||||
|
||||
// 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) {
|
||||
chart.useInteractiveGuideline(true);
|
||||
}
|
||||
|
@ -153,19 +163,17 @@ function viz_nvd3(data_attribute) {
|
|||
|
||||
chart.duration(0);
|
||||
|
||||
token.select('.chart').append("svg")
|
||||
d3.select(slice.selector).append("svg")
|
||||
.datum(data.chart_data)
|
||||
.transition().duration(500)
|
||||
.call(chart);
|
||||
|
||||
return chart;
|
||||
});
|
||||
chart_div.show();
|
||||
ctrl.done(data);
|
||||
slice.done(data);
|
||||
})
|
||||
.fail(function(xhr) {
|
||||
chart_div.show();
|
||||
ctrl.error(xhr.responseText);
|
||||
slice.error(xhr.responseText);
|
||||
});
|
||||
};
|
||||
var resize = function() {
|
||||
|
@ -189,5 +197,5 @@ function viz_nvd3(data_attribute) {
|
|||
'line',
|
||||
'pie',
|
||||
].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;
|
||||
}
|
||||
|
|
|
@ -1,23 +1,20 @@
|
|||
px.registerWidget('pivot_table', function(data_attribute) {
|
||||
var token_name = data_attribute['token'];
|
||||
var token = $('#' + token_name);
|
||||
var form_data = data_attribute.form_data;
|
||||
px.registerViz('pivot_table', function(slice) {
|
||||
container = slice.container;
|
||||
var form_data = slice.data.form_data;
|
||||
|
||||
function refresh(ctrl) {
|
||||
$.getJSON(data_attribute.json_endpoint, function(json){
|
||||
token.html(json.data);
|
||||
function refresh() {
|
||||
$.getJSON(slice.jsonEndpoint(), function(json){
|
||||
container.html(json.data);
|
||||
if (form_data.groupby.length == 1){
|
||||
var table = token.find('table').DataTable({
|
||||
var table = container.find('table').DataTable({
|
||||
paging: false,
|
||||
searching: false,
|
||||
});
|
||||
table.column('-1').order( 'desc' ).draw();
|
||||
}
|
||||
token.show();
|
||||
ctrl.done(json);
|
||||
slice.done(json);
|
||||
}).fail(function(xhr){
|
||||
token.show();
|
||||
ctrl.error(xhr.responseText);
|
||||
slice.error(xhr.responseText);
|
||||
});
|
||||
}
|
||||
return {
|
||||
|
|
|
@ -1,28 +1,21 @@
|
|||
#chart {
|
||||
height: 100%;
|
||||
}
|
||||
div.token {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.node rect {
|
||||
.sankey .node rect {
|
||||
cursor: move;
|
||||
fill-opacity: .9;
|
||||
shape-rendering: crispEdges;
|
||||
}
|
||||
|
||||
.node text {
|
||||
.sankey .node text {
|
||||
pointer-events: none;
|
||||
text-shadow: 0 1px 0 #fff;
|
||||
}
|
||||
|
||||
.link {
|
||||
.sankey .link {
|
||||
fill: none;
|
||||
stroke: #000;
|
||||
stroke-opacity: .2;
|
||||
}
|
||||
|
||||
.link:hover {
|
||||
.sankey .link:hover {
|
||||
stroke-opacity: .5;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
function viz_sankey(data_attribute) {
|
||||
var token = d3.select('#' + data_attribute.token);
|
||||
var div = token.select("#chart");
|
||||
var xy = div.node().getBoundingClientRect();
|
||||
var width = xy.width;
|
||||
var height = xy.height - 25;
|
||||
function viz_sankey(slice) {
|
||||
var div = d3.select(slice.selector);
|
||||
|
||||
var render = function(ctrl) {
|
||||
var margin = {top: 1, right: 1, bottom: 6, left: 1};
|
||||
var render = function() {
|
||||
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;
|
||||
height = height - margin.top - margin.bottom;
|
||||
|
||||
|
@ -14,7 +12,7 @@ function viz_sankey(data_attribute) {
|
|||
format = function(d) { return formatNumber(d) + " TWh"; },
|
||||
color = d3.scale.category20();
|
||||
|
||||
var svg = token.select("#chart").append("svg")
|
||||
var svg = div.append("svg")
|
||||
.attr("width", width + margin.left + margin.right)
|
||||
.attr("height", height + margin.top + margin.bottom)
|
||||
.append("g")
|
||||
|
@ -26,9 +24,9 @@ function viz_sankey(data_attribute) {
|
|||
|
||||
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){
|
||||
ctrl.error(error.responseText);
|
||||
slice.error(error.responseText);
|
||||
return '';
|
||||
}
|
||||
links = json.data;
|
||||
|
@ -95,8 +93,7 @@ function viz_sankey(data_attribute) {
|
|||
sankey.relayout();
|
||||
link.attr("d", path);
|
||||
}
|
||||
token.select("img.loading").remove();
|
||||
ctrl.done(json);
|
||||
slice.done(json);
|
||||
});
|
||||
}
|
||||
return {
|
||||
|
@ -104,4 +101,4 @@ function viz_sankey(data_attribute) {
|
|||
resize: render,
|
||||
};
|
||||
}
|
||||
px.registerWidget('sankey', viz_sankey);
|
||||
px.registerViz('sankey', viz_sankey);
|
||||
|
|
|
@ -1,32 +1,19 @@
|
|||
#sidebar {
|
||||
float: right;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
text.middle{
|
||||
.sunburst text.middle{
|
||||
text-anchor: middle;
|
||||
}
|
||||
|
||||
#sequence {
|
||||
.sunburst #sequence {
|
||||
}
|
||||
|
||||
#legend {
|
||||
.sunburst #legend {
|
||||
padding: 10px 0 0 3px;
|
||||
}
|
||||
|
||||
#sequence text, #legend text {
|
||||
.sunburst #sequence text, #legend text {
|
||||
font-weight: 600;
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
.sunburst #chart {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.sunburst div.token {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.sunburst path {
|
||||
stroke: #fff;
|
||||
}
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
Modified from http://bl.ocks.org/kerryrodden/7090426
|
||||
*/
|
||||
|
||||
function viz_sunburst(data_attribute) {
|
||||
var token = d3.select('#' + data_attribute.token);
|
||||
var render = function(ctrl) {
|
||||
function viz_sunburst(slice) {
|
||||
var container = d3.select(slice.selector);
|
||||
var render = function() {
|
||||
// Breadcrumb dimensions: width, height, spacing, width of tip/tail.
|
||||
var b = {
|
||||
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.
|
||||
var totalSize = 0;
|
||||
var div = token.select("#chart");
|
||||
var xy = div.node().getBoundingClientRect();
|
||||
var width = xy.width;
|
||||
var height = xy.height - 25;
|
||||
var width = slice.container.width();
|
||||
var height = slice.container.height() - 25;
|
||||
var radius = Math.min(width, height) / 2;
|
||||
|
||||
var vis = div.append("svg:svg")
|
||||
var vis = container.append("svg:svg")
|
||||
.attr("width", width)
|
||||
.attr("height", height)
|
||||
.append("svg:g")
|
||||
|
@ -39,15 +37,15 @@ function viz_sunburst(data_attribute) {
|
|||
.outerRadius(function(d) { return Math.sqrt(d.y + d.dy); });
|
||||
|
||||
var ext;
|
||||
d3.json(data_attribute.json_endpoint, function(error, json){
|
||||
d3.json(slice.data.json_endpoint, function(error, json){
|
||||
|
||||
if (error != null){
|
||||
ctrl.error(error.responseText);
|
||||
slice.error(error.responseText);
|
||||
return '';
|
||||
}
|
||||
var tree = buildHierarchy(json.data);
|
||||
createVisualization(tree);
|
||||
ctrl.done(json);
|
||||
slice.done(json);
|
||||
});
|
||||
|
||||
// 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.
|
||||
token.select("#container").on("mouseleave", mouseleave);
|
||||
container.select("#container").on("mouseleave", mouseleave);
|
||||
|
||||
// Get total size of the tree = value of root node from partition.
|
||||
totalSize = path.node().__data__.value;
|
||||
|
@ -117,12 +115,12 @@ function viz_sunburst(data_attribute) {
|
|||
updateBreadcrumbs(sequenceArray, percentageString);
|
||||
|
||||
// Fade all the segments.
|
||||
token.selectAll("path")
|
||||
container.selectAll("path")
|
||||
.style("stroke-width", "1px")
|
||||
.style("opacity", 0.3);
|
||||
|
||||
// Then highlight only those that are an ancestor of the current segment.
|
||||
token.selectAll("path")
|
||||
container.selectAll("path")
|
||||
.filter(function(node) {
|
||||
return (sequenceArray.indexOf(node) >= 0);
|
||||
})
|
||||
|
@ -135,16 +133,16 @@ function viz_sunburst(data_attribute) {
|
|||
function mouseleave(d) {
|
||||
|
||||
// Hide the breadcrumb trail
|
||||
token.select("#trail")
|
||||
container.select("#trail")
|
||||
.style("visibility", "hidden");
|
||||
gMiddleText.selectAll("*").remove();
|
||||
|
||||
// Deactivate all segments during transition.
|
||||
token.selectAll("path").on("mouseenter", null);
|
||||
container.selectAll("path").on("mouseenter", null);
|
||||
//gMiddleText.selectAll("*").remove();
|
||||
|
||||
// Transition each segment to full opacity and then reactivate it.
|
||||
token.selectAll("path")
|
||||
container.selectAll("path")
|
||||
.transition()
|
||||
.duration(200)
|
||||
.style("opacity", 1)
|
||||
|
@ -253,4 +251,4 @@ function viz_sunburst(data_attribute) {
|
|||
resize: render,
|
||||
};
|
||||
}
|
||||
px.registerWidget('sunburst', viz_sunburst);
|
||||
px.registerViz('sunburst', viz_sunburst);
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
li.widget.table div.token {
|
||||
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'];
|
||||
var token = $('#' + token_name);
|
||||
|
||||
function refresh(ctrl) {
|
||||
$.getJSON(data_attribute.json_endpoint, function(json){
|
||||
function refresh() {
|
||||
var f = d3.format('.3s');
|
||||
$.getJSON(slice.jsonEndpoint(), function(json){
|
||||
var data = json.data;
|
||||
var metrics = json.form_data.metrics;
|
||||
function col(c){
|
||||
|
@ -18,8 +18,7 @@ px.registerWidget('table', function(data_attribute) {
|
|||
for (var i=0; i<metrics.length; i++){
|
||||
maxes[metrics[i]] = d3.max(col(metrics[i]));
|
||||
}
|
||||
|
||||
var table = d3.select('#' + token_name).append('table')
|
||||
var table = d3.select(slice.selector).append('table')
|
||||
.attr('class', 'dataframe table table-striped table-bordered table-condensed table-hover');
|
||||
table.append('thead').append('tr')
|
||||
.selectAll('th')
|
||||
|
@ -32,27 +31,57 @@ px.registerWidget('table', function(data_attribute) {
|
|||
.append('tr')
|
||||
.selectAll('td')
|
||||
.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()
|
||||
.append('td')
|
||||
.style('background-image', function(d){
|
||||
var perc = Math.round((d[1][d[0]] / maxes[d[0]]) * 100);
|
||||
if (perc !== NaN)
|
||||
return "linear-gradient(to right, lightgrey, lightgrey " + perc + "%, rgba(0,0,0,0) " + perc + "%"
|
||||
if (d.isMetric){
|
||||
var perc = Math.round((d.val / maxes[d.col]) * 100);
|
||||
return "linear-gradient(to right, lightgrey, lightgrey " + perc + "%, rgba(0,0,0,0) " + perc + "%";
|
||||
}
|
||||
})
|
||||
.html(function(d){return d[1][d[0]]});
|
||||
var datatable = token.find('table').DataTable({
|
||||
.attr('data-sort', function(d){
|
||||
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,
|
||||
searching: data_attribute.form_data.include_search,
|
||||
searching: form_data.include_search,
|
||||
});
|
||||
// Sorting table by main column
|
||||
if (data_attribute.form_data.metrics.length > 0) {
|
||||
var main_metric = data_attribute.form_data.metrics[0];
|
||||
if (form_data.metrics.length > 0) {
|
||||
var main_metric = form_data.metrics[0];
|
||||
datatable.column(data.columns.indexOf(main_metric)).order( 'desc' ).draw();
|
||||
}
|
||||
ctrl.done(json);
|
||||
slice.done(json);
|
||||
}).fail(function(xhr){
|
||||
ctrl.error(xhr.responseText);
|
||||
slice.error(xhr.responseText);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
px.registerWidget('word_cloud', function(data_attribute) {
|
||||
|
||||
var token_name = data_attribute['token'];
|
||||
var json_callback = data_attribute['json_endpoint'];
|
||||
var token = d3.select('#' + token_name);
|
||||
|
||||
function refresh(ctrl) {
|
||||
d3.json(json_callback, function(error, json) {
|
||||
px.registerViz('word_cloud', function(slice) {
|
||||
var slice = slice;
|
||||
var chart = d3.select(slice.selector);
|
||||
function refresh() {
|
||||
d3.json(slice.jsonEndpoint(), function(error, json) {
|
||||
if (error != null){
|
||||
ctrl.error(error.responseText);
|
||||
slice.error(error.responseText);
|
||||
return '';
|
||||
}
|
||||
var data = json.data;
|
||||
|
@ -25,8 +22,7 @@ px.registerWidget('word_cloud', function(data_attribute) {
|
|||
else {
|
||||
var f_rotation = function() { return (~~(Math.random() * 6) - 3) * 30; };
|
||||
}
|
||||
var box = token.node().getBoundingClientRect();
|
||||
var size = [box.width, box.height - 25];
|
||||
var size = [slice.container.width(), slice.container.height() - 25];
|
||||
|
||||
scale = d3.scale.linear()
|
||||
.range(range)
|
||||
|
@ -42,9 +38,9 @@ px.registerWidget('word_cloud', function(data_attribute) {
|
|||
.on("end", draw);
|
||||
layout.start();
|
||||
function draw(words) {
|
||||
token.selectAll("*").remove();
|
||||
chart.selectAll("*").remove();
|
||||
|
||||
token.append("svg")
|
||||
chart.append("svg")
|
||||
.attr("width", layout.size()[0])
|
||||
.attr("height", layout.size()[1])
|
||||
.append("g")
|
||||
|
@ -61,7 +57,7 @@ px.registerWidget('word_cloud', function(data_attribute) {
|
|||
})
|
||||
.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,
|
||||
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/
|
||||
*/
|
||||
|
||||
function viz_world_map(data_attribute) {
|
||||
var token = d3.select('#' + data_attribute.token);
|
||||
var render = function(ctrl) {
|
||||
// Breadcrumb dimensions: width, height, spacing, width of tip/tail.
|
||||
var div = token;
|
||||
var xy = div.node().getBoundingClientRect();
|
||||
var width = xy.width;
|
||||
var height = xy.height - 25;
|
||||
function viz_world_map(slice) {
|
||||
var render = function() {
|
||||
var container = slice.container;
|
||||
var div = d3.select(slice.selector);
|
||||
|
||||
d3.json(data_attribute.json_endpoint, function(error, json){
|
||||
d3.json(slice.data.json_endpoint, function(error, json){
|
||||
|
||||
if (error != null){
|
||||
ctrl.error(error.responseText);
|
||||
slice.error(error.responseText);
|
||||
return '';
|
||||
}
|
||||
var ext = d3.extent(json.data, function(d){return d.m1});
|
||||
var extRadius = d3.extent(json.data, function(d){return d.m2});
|
||||
var radiusScale = d3.scale.linear()
|
||||
.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){
|
||||
d.radius = radiusScale(d.m2);
|
||||
})
|
||||
|
@ -35,11 +31,12 @@ function viz_world_map(data_attribute) {
|
|||
d[country.country] = country;
|
||||
}
|
||||
f = d3.format('.3s');
|
||||
container.show();
|
||||
var map = new Datamap({
|
||||
element: document.getElementById(data_attribute.token),
|
||||
element: slice.container.get(0),
|
||||
data: json.data,
|
||||
fills: {
|
||||
defaultFill: 'grey'
|
||||
defaultFill: 'transparent'
|
||||
},
|
||||
geographyConfig: {
|
||||
popupOnHover: true,
|
||||
|
@ -75,11 +72,11 @@ function viz_world_map(data_attribute) {
|
|||
},
|
||||
});
|
||||
map.updateChoropleth(d);
|
||||
if(data_attribute.form_data.show_bubbles){
|
||||
if(slice.data.form_data.show_bubbles){
|
||||
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,
|
||||
};
|
||||
}
|
||||
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">
|
||||
{{ dashboard.css }}
|
||||
</style>
|
||||
{% for slice in dashboard.slices %}
|
||||
{% set viz = slice.viz %}
|
||||
{% import viz.template as viz_macros %}
|
||||
{{ viz_macros.viz_css(viz) }}
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content_fluid %}
|
||||
|
@ -77,17 +72,16 @@ body {
|
|||
{% for slice in dashboard.slices %}
|
||||
{% set pos = pos_dict.get(slice.id, {}) %}
|
||||
{% set viz = slice.viz %}
|
||||
{% import viz.template as viz_macros %}
|
||||
<li
|
||||
id="slice_{{ slice.id }}"
|
||||
slice_id="{{ slice.id }}"
|
||||
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-col="{{ pos.col or loop.index }}"
|
||||
data-sizex="{{ pos.size_x or 4 }}"
|
||||
data-sizey="{{ pos.size_y or 4 }}">
|
||||
<table class="widget_header">
|
||||
<table class="slice_header">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="icons">
|
||||
|
@ -103,7 +97,7 @@ body {
|
|||
<nobr>
|
||||
<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 class="closewidget"><i class="fa fa-close"></i></a>
|
||||
<a class="closeslice"><i class="fa fa-close"></i></a>
|
||||
</br>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -111,7 +105,7 @@ body {
|
|||
</table>
|
||||
<div id="{{ viz.token }}" class="token" style="height: 100%;">
|
||||
<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>
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
@ -127,19 +121,9 @@ body {
|
|||
{% endfor %}
|
||||
<script src="/static/lib/gridster/jquery.gridster.with-extras.min.js"></script>
|
||||
<script>
|
||||
$(document).ready(px.initializeDashboardView({{ dashboard.id }}));
|
||||
$(document).ready(function() {
|
||||
$('.dashboard .widget').each(function() {
|
||||
var data = $(this).data('widget');
|
||||
var widget = px.initializeWidget(data);
|
||||
$(this).data('widget', widget);
|
||||
widget.render();
|
||||
});
|
||||
px.initDashboardView();
|
||||
var dashboard = px.Dashboard({{ dashboard.id }});
|
||||
});
|
||||
</script>
|
||||
{% for slice in dashboard.slices %}
|
||||
{% set viz = slice.viz %}
|
||||
{% import viz.template as viz_macros %}
|
||||
{{ viz_macros.viz_js(viz) }}
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -26,22 +26,29 @@
|
|||
<span class="btn btn-default notbtn">
|
||||
<strong>{{ datasource.full_name }}</strong>
|
||||
{% 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>
|
||||
</a>
|
||||
{% endif %}
|
||||
<a class="" href="/{{ datasource.baselink }}/edit/{{ datasource.id }}" data-toggle="tooltip" title="Edit datasource">
|
||||
<i class="fa fa-edit"></i>
|
||||
</a>
|
||||
</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">
|
||||
<span class="btn btn-default disabled">
|
||||
<i class="fa fa-file-text"></i>
|
||||
<span class="btn btn-default" id="standalone" title="Standalone version, use to embed anywhere" data-toggle="tooltip">
|
||||
<i class="fa fa-code"></i>
|
||||
</span>
|
||||
<span class="btn btn-default" id="csv">.csv</span>
|
||||
<span class="btn btn-default" id="json">.json</span>
|
||||
<span class="btn btn-default " id="json" title="Export to .json" data-toggle="tooltip">
|
||||
<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>
|
||||
<hr/>
|
||||
</div>
|
||||
|
@ -133,23 +140,15 @@
|
|||
</div>
|
||||
|
||||
<div class="col-md-9">
|
||||
{% block messages %}
|
||||
{% endblock %}
|
||||
{% include 'appbuilder/flash.html' %}
|
||||
{% block messages %}{% endblock %}
|
||||
{% include 'appbuilder/flash.html' %}
|
||||
<div
|
||||
id="{{ viz.token }}"
|
||||
class="viz widget {{ viz.viz_type }}"
|
||||
data-widget="{{ viz.get_data_attribute() }}"
|
||||
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">
|
||||
{% block viz_html %}
|
||||
{% 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 id="{{ viz.token }}_con" class="slice_container" style="height: 100%; width: 100%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -176,6 +175,24 @@
|
|||
</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>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -183,15 +200,15 @@
|
|||
{% block tail_js %}
|
||||
{{ super() }}
|
||||
<script>
|
||||
$(document).ready(px.initializeDatasourceView);
|
||||
$(document).ready(px.initExploreView);
|
||||
$(document).ready(function() {
|
||||
var data = $('.widget').data('widget');
|
||||
var widget = px.initializeWidget(data);
|
||||
$('.widget').data('widget', widget);
|
||||
widget.render();
|
||||
var data = $('.slice').data('slice');
|
||||
var slice = px.Slice(data);
|
||||
$('.slice').data('slice', slice);
|
||||
slice.render();
|
||||
|
||||
function get_collapsed_fieldsets(){
|
||||
collapsed_fieldsets = $("#collapsed_fieldsets").val()
|
||||
collapsed_fieldsets = $("#collapsed_fieldsets").val();
|
||||
if (collapsed_fieldsets != undefined && collapsed_fieldsets != "")
|
||||
collapsed_fieldsets = collapsed_fieldsets.split('||');
|
||||
else
|
||||
|
@ -242,9 +259,12 @@
|
|||
$('#csv').click(function () {
|
||||
window.location = '{{ viz.csv_endpoint | safe }}';
|
||||
});
|
||||
$('#standalone').click(function () {
|
||||
window.location = '{{ viz.standalone_endpoint | safe }}';
|
||||
});
|
||||
$("#viz_type").change(function() {$("#query").submit();});
|
||||
collapsed_fieldsets = get_collapsed_fieldsets();
|
||||
for(var i=0; i<collapsed_fieldsets.length;i++){
|
||||
for(var i=0; i < collapsed_fieldsets.length; i++){
|
||||
toggle_fieldset($('legend:contains("' + collapsed_fieldsets[i] + '")'), false);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
{% extends "panoramix/base.html" %}
|
||||
{% block content %}
|
||||
<h1><i class='fa fa-star'></i> Featured Datasets </h1>
|
||||
<div class="header">
|
||||
<h1><i class='fa fa-star'></i> Featured Datasets </h1>
|
||||
</div>
|
||||
<hr/>
|
||||
<table class="table table-hover dataTable" id="dataset-table" style="display:None">
|
||||
<thead>
|
||||
|
@ -25,6 +27,7 @@
|
|||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<hr/>
|
||||
{% endblock %}
|
||||
|
||||
{% block head_css %}
|
||||
|
@ -42,6 +45,7 @@
|
|||
"bPaginate": false,
|
||||
"order": [[ 1, "asc" ]]
|
||||
});
|
||||
$('#dataset-table_info').remove();
|
||||
$('#dataset-table').show();
|
||||
} );
|
||||
</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" %}
|
||||
{{ viz.get_json() }}
|
||||
{% else %}
|
||||
{% if viz.request.args.get("standalone") == "true" %}
|
||||
{% extends 'panoramix/viz_standalone.html' %}
|
||||
{% extends 'panoramix/standalone.html' %}
|
||||
{% else %}
|
||||
{% extends 'panoramix/explore.html' %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% block viz_html %}
|
||||
{{ viz_macros.viz_html(viz) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block head_css %}
|
||||
{{super()}}
|
||||
{% if viz.request.args.get("skip_libs") != "true" %}
|
||||
{% for css in viz.css_files %}
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename=css) }}">
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{{ viz_macros.viz_css(viz) }}
|
||||
{% for css in viz.css_files %}
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename=css) }}">
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block tail %}
|
||||
{{super()}}
|
||||
{% if viz.request.args.get("skip_libs") != "true" %}
|
||||
{% for js in viz.js_files %}
|
||||
<script src="{{ url_for('static', filename=js) }}"></script>
|
||||
{% endfor %}
|
||||
{{ viz_macros.viz_js(viz) }}
|
||||
{% endif %}
|
||||
{% for js in viz.js_files %}
|
||||
<script src="{{ url_for('static', filename=js) }}"></script>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
{% 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]:
|
||||
dttm = dttm[5:]
|
||||
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 markdown import markdown
|
||||
from pandas.io.json import dumps
|
||||
from pandas.io.json import dumps, to_json
|
||||
from werkzeug.datastructures import ImmutableMultiDict
|
||||
from werkzeug.urls import Href
|
||||
import numpy as np
|
||||
|
@ -22,7 +22,6 @@ config = app.config
|
|||
class BaseViz(object):
|
||||
viz_type = None
|
||||
verbose_name = "Base Viz"
|
||||
template = None
|
||||
is_timeseries = False
|
||||
fieldsets = (
|
||||
{
|
||||
|
@ -157,6 +156,14 @@ class BaseViz(object):
|
|||
eq = form_data.get("flt_eq_" + str(i))
|
||||
if col and op and 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
|
||||
|
||||
def query_obj(self):
|
||||
|
@ -176,7 +183,7 @@ class BaseViz(object):
|
|||
from_dttm = datetime.now() - (from_dttm-datetime.now())
|
||||
until = form_data.get("until", "now")
|
||||
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")
|
||||
from_dttm = to_dttm # Making them identical to not raise
|
||||
|
||||
|
@ -205,6 +212,9 @@ class BaseViz(object):
|
|||
'data': json.loads(self.get_json_data()),
|
||||
'query': self.query,
|
||||
'form_data': self.form_data,
|
||||
'json_endpoint': self.json_endpoint,
|
||||
'csv_endpoint': self.csv_endpoint,
|
||||
'standalone_endpoint': self.standalone_endpoint,
|
||||
}
|
||||
return json.dumps(payload)
|
||||
|
||||
|
@ -223,19 +233,27 @@ class BaseViz(object):
|
|||
def csv_endpoint(self):
|
||||
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 = {
|
||||
'viz_name': self.viz_type,
|
||||
'json_endpoint': self.json_endpoint,
|
||||
'token': self.token,
|
||||
'form_data': self.form_data,
|
||||
}
|
||||
return dumps(content)
|
||||
return content
|
||||
|
||||
@property
|
||||
def json_data(self):
|
||||
return dumps(self.data)
|
||||
|
||||
class TableViz(BaseViz):
|
||||
viz_type = "table"
|
||||
verbose_name = "Table View"
|
||||
template = 'panoramix/viz_table.html'
|
||||
fieldsets = (
|
||||
{
|
||||
'label': None,
|
||||
|
@ -293,16 +311,18 @@ class TableViz(BaseViz):
|
|||
|
||||
def get_json_data(self):
|
||||
df = self.get_df()
|
||||
return dumps(dict(
|
||||
records=df.to_dict(orient="records"),
|
||||
columns=df.columns,
|
||||
))
|
||||
return json.dumps(
|
||||
dict(
|
||||
records=df.to_dict(orient="records"),
|
||||
columns=list(df.columns),
|
||||
),
|
||||
default=utils.json_iso_dttm_ser,
|
||||
)
|
||||
|
||||
|
||||
class PivotTableViz(BaseViz):
|
||||
viz_type = "pivot_table"
|
||||
verbose_name = "Pivot Table"
|
||||
template = 'panoramix/viz_pivot_table.html'
|
||||
css_files = [
|
||||
'lib/dataTables/dataTables.bootstrap.css',
|
||||
'widgets/viz_pivot_table.css']
|
||||
|
@ -373,7 +393,6 @@ class PivotTableViz(BaseViz):
|
|||
class MarkupViz(BaseViz):
|
||||
viz_type = "markup"
|
||||
verbose_name = "Markup Widget"
|
||||
template = 'panoramix/viz_markup.html'
|
||||
js_files = ['widgets/viz_markup.js']
|
||||
fieldsets = (
|
||||
{
|
||||
|
@ -390,6 +409,9 @@ class MarkupViz(BaseViz):
|
|||
elif markup_type == "html":
|
||||
return code
|
||||
|
||||
def get_json_data(self):
|
||||
return dumps(dict(html=self.rendered()))
|
||||
|
||||
|
||||
class WordCloudViz(BaseViz):
|
||||
"""
|
||||
|
@ -398,7 +420,6 @@ class WordCloudViz(BaseViz):
|
|||
"""
|
||||
viz_type = "word_cloud"
|
||||
verbose_name = "Word Cloud"
|
||||
template = 'panoramix/viz_word_cloud.html'
|
||||
is_timeseries = False
|
||||
fieldsets = (
|
||||
{
|
||||
|
@ -435,7 +456,6 @@ class WordCloudViz(BaseViz):
|
|||
class NVD3Viz(BaseViz):
|
||||
viz_type = None
|
||||
verbose_name = "Base NVD3 Viz"
|
||||
template = 'panoramix/viz_nvd3.html'
|
||||
is_timeseries = False
|
||||
js_files = [
|
||||
'lib/d3.min.js',
|
||||
|
@ -458,8 +478,8 @@ class BubbleViz(NVD3Viz):
|
|||
'fields': (
|
||||
('since', 'until'),
|
||||
('series', 'entity'),
|
||||
('x', 'y'),
|
||||
('size', 'limit'),
|
||||
'x', 'y', 'size',
|
||||
'limit',
|
||||
('x_log_scale', 'y_log_scale'),
|
||||
('show_legend', None),
|
||||
)
|
||||
|
@ -517,7 +537,6 @@ class BubbleViz(NVD3Viz):
|
|||
class BigNumberViz(BaseViz):
|
||||
viz_type = "big_number"
|
||||
verbose_name = "Big Number"
|
||||
template = 'panoramix/viz_bignumber.html'
|
||||
is_timeseries = True
|
||||
js_files = [
|
||||
'lib/d3.min.js',
|
||||
|
@ -850,7 +869,6 @@ class SunburstViz(BaseViz):
|
|||
viz_type = "sunburst"
|
||||
verbose_name = "Sunburst"
|
||||
is_timeseries = False
|
||||
template = 'panoramix/viz_sunburst.html'
|
||||
js_files = [
|
||||
'lib/d3.min.js',
|
||||
'widgets/viz_sunburst.js']
|
||||
|
@ -917,7 +935,6 @@ class SankeyViz(BaseViz):
|
|||
viz_type = "sankey"
|
||||
verbose_name = "Sankey"
|
||||
is_timeseries = False
|
||||
template = 'panoramix/viz_sankey.html'
|
||||
js_files = [
|
||||
'lib/d3.min.js',
|
||||
'lib/d3-sankey.js',
|
||||
|
@ -934,10 +951,17 @@ class SankeyViz(BaseViz):
|
|||
'row_limit',
|
||||
)
|
||||
},)
|
||||
form_overrides = {}
|
||||
form_overrides = {
|
||||
'groupby': {
|
||||
'label': 'Source / Target',
|
||||
'description': "Choose a source and a target",
|
||||
},
|
||||
}
|
||||
|
||||
def query_obj(self):
|
||||
qry = super(SankeyViz, self).query_obj()
|
||||
if len(qry['groupby']) != 2:
|
||||
raise Exception("Pick exactly 2 columns as [Source / Target]")
|
||||
qry['metrics'] = [
|
||||
self.form_data['metric']]
|
||||
return qry
|
||||
|
@ -953,7 +977,6 @@ class DirectedForceViz(BaseViz):
|
|||
viz_type = "directed_force"
|
||||
verbose_name = "Directed Force Layout"
|
||||
is_timeseries = False
|
||||
template = 'panoramix/viz_directed_force.html'
|
||||
js_files = [
|
||||
'lib/d3.min.js',
|
||||
'widgets/viz_directed_force.js']
|
||||
|
@ -1000,7 +1023,6 @@ class WorldMapViz(BaseViz):
|
|||
viz_type = "world_map"
|
||||
verbose_name = "World Map"
|
||||
is_timeseries = False
|
||||
template = 'panoramix/viz_world_map.html'
|
||||
js_files = [
|
||||
'lib/d3.min.js',
|
||||
'lib/topojson.min.js',
|
||||
|
|
|
@ -2,4 +2,4 @@
|
|||
rm /tmp/panoramix_unittests.db
|
||||
export PANORAMIX_CONFIG=tests.panoramix_test_config
|
||||
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