Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion caravel/assets/javascripts/modules/caravel.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ var sourceMap = {
table: 'table.js',
word_cloud: 'word_cloud.js',
world_map: 'world_map.js',
treemap: 'treemap.js'
treemap: 'treemap.js',
cal_heatmap: 'cal_heatmap.js'
};

var color = function () {
Expand Down
3 changes: 3 additions & 0 deletions caravel/assets/visualizations/cal_heatmap.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.cal_heatmap .slice_container {
padding: 10px;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

\n

58 changes: 58 additions & 0 deletions caravel/assets/visualizations/cal_heatmap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// JS
var d3 = window.d3 || require('d3');

// CSS
require('./cal_heatmap.css');
require('../node_modules/cal-heatmap/cal-heatmap.css');

var CalHeatMap = require('cal-heatmap');

function calHeatmap(slice) {

var div = d3.select(slice.selector);
var cal = new CalHeatMap();

var render = function () {
d3.json(slice.jsonEndpoint(), function (error, json) {

if (error !== null) {
slice.error(error.responseText);
return '';
}

div.selectAll("*").remove();
cal = new CalHeatMap();

var timestamps = json.data["timestamps"],
extents = d3.extent(Object.keys(timestamps), function (key) {
return timestamps[key];
}),
step = (extents[1] - extents[0]) / 5;

try {
cal.init({
start: json.data["start"],
data: timestamps,
itemSelector: slice.selector,
tooltip: true,
domain: json.data["domain"],
subDomain: json.data["subdomain"],
range: json.data["range"],
browsing: true,
legend: [extents[0], extents[0]+step, extents[0]+step*2, extents[0]+step*3]
});
} catch (e) {
slice.error(e);
}

slice.done(json);
});
};

return {
render: render,
resize: render
};
}

module.exports = calHeatmap;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

\n

1 change: 0 additions & 1 deletion caravel/assets/visualizations/treemap.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ function treemap(slice) {
return results;
};


// Compute the treemap layout recursively such that each group of siblings
// uses the same size (1x1) rather than the dimensions of the parent cell.
// This optimizes the layout for the current zoom state. Note that a wrapper
Expand Down
3 changes: 3 additions & 0 deletions caravel/bin/caravel
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ def load_examples(load_test_data):
print("Loading [Birth names]")
data.load_birth_names()

print("Loading [Random time series data]")
data.load_random_time_series_data()

if load_test_data:
print("Loading [Unicode test data]")
data.load_unicode_test_data()
Expand Down
54 changes: 54 additions & 0 deletions caravel/data/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -895,3 +895,57 @@ def load_unicode_test_data():
dash.slices = [slc]
db.session.merge(dash)
db.session.commit()


def load_random_time_series_data():
"""Loading random time series data from a zip file in the repo"""
with gzip.open(os.path.join(DATA_FOLDER, 'random_time_series.json.gz')) as f:
pdf = pd.read_json(f)
pdf.ds = pd.to_datetime(pdf.ds, unit='s')
pdf.to_sql(
'random_time_series',
db.engine,
if_exists='replace',
chunksize=500,
dtype={
'ds': DateTime,
},
index=False)
print("Done loading table!")
print("-" * 80)

print("Creating table reference")
obj = db.session.query(TBL).filter_by(table_name='random_time_series').first()
if not obj:
obj = TBL(table_name='random_time_series')
obj.main_dttm_col = 'ds'
obj.database = get_or_create_db(db.session)
obj.is_featured = False
db.session.merge(obj)
db.session.commit()
obj.fetch_metadata()
tbl = obj

slice_data = {
"datasource_id": "6",
"datasource_name": "random_time_series",
"datasource_type": "table",
"granularity": "day",
"row_limit": config.get("ROW_LIMIT"),
"since": "1 year ago",
"until": "now",
"where": "",
"viz_type": "cal_heatmap",
"domain_granularity": "month",
"subdomain_granularity": "day",
}

print("Creating a slice")
slc = Slice(
slice_name="Calendar Heatmap",
viz_type='cal_heatmap',
datasource_type='table',
table=tbl,
params=get_slice_json(slice_data),
)
merge_slice(slc)
Binary file added caravel/data/random_time_series.json.gz
Binary file not shown.
23 changes: 23 additions & 0 deletions caravel/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,29 @@ def __init__(self, viz):
"The time granularity for the visualization. Note that you "
"can type and use simple natural language as in '10 seconds', "
"'1 day' or '56 weeks'")),
'domain_granularity': SelectField(
'Domain', default="month",
choices=self.choicify([
'hour',
'day',
'week',
'month',
'year',
]),
description=(
"The time unit used for the grouping of blocks")),
'subdomain_granularity': SelectField(
'Subdomain', default="day",
choices=self.choicify([
'min',
'hour',
'day',
'week',
'month',
]),
description=(
"The time unit for each block. Should be a smaller unit than "
"domain_granularity. Should be larger or equal to Time Grain")),
'link_length': FreeFormSelectField(
'Link Length', default="200",
choices=self.choicify([
Expand Down
6 changes: 6 additions & 0 deletions caravel/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,12 @@ def grains(self):
Grain("month", "DATE(DATE_SUB({col}, "
"INTERVAL DAYOFMONTH({col}) - 1 DAY))"),
),
'sqlite': (
Grain('Time Column', '{col}'),
Grain('day', 'DATE({col})'),
Grain("week", "DATE({col}, -strftime('%w', {col}) || ' days')"),
Grain("month", "DATE({col}, -strftime('%d', {col}) || ' days')"),
),
'postgresql': (
Grain("Time Column", "{col}"),
Grain("second", "DATE_TRUNC('second', {col})"),
Expand Down
63 changes: 63 additions & 0 deletions caravel/viz.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from six import string_types
from werkzeug.datastructures import ImmutableMultiDict
from werkzeug.urls import Href
from dateutil import relativedelta as rdelta

from caravel import app, utils, cache
from caravel.forms import FormFactory
Expand Down Expand Up @@ -541,6 +542,67 @@ def get_data(self):
return chart_data


class CalHeatmapViz(BaseViz):

"""Calendar heatmap."""

viz_type = "cal_heatmap"
verbose_name = "Calender Heatmap"
credits = (
'<a href=https://github.com/wa0x6e/cal-heatmap>cal-heatmap</a>')
is_timeseries = True
fieldsets = ({
'label': None,
'fields': (
'metric',
'domain_granularity',
'subdomain_granularity',
),
},)

def get_df(self, query_obj=None):
df = super(CalHeatmapViz, self).get_df(query_obj)
return df

def get_data(self):
df = self.get_df()
form_data = self.form_data

df.columns = ["timestamp", "metric"]
timestamps = {str(obj["timestamp"].value / 10**9):
obj.get("metric") for obj in df.to_dict("records")}

start = utils.parse_human_datetime(form_data.get("since"))
end = utils.parse_human_datetime(form_data.get("until"))
domain = form_data.get("domain_granularity")
diff_delta = rdelta.relativedelta(end, start)
diff_secs = (end - start).total_seconds()

if domain == "year":
range_ = diff_delta.years + 1
elif domain == "month":
range_ = diff_delta.years * 12 + diff_delta.months + 1
elif domain == "week":
range_ = diff_delta.years * 53 + diff_delta.weeks + 1
elif domain == "day":
range_ = diff_secs // (24*60*60) + 1
else:
range_ = diff_secs // (60*60) + 1

return {
"timestamps": timestamps,
"start": start,
"domain": domain,
"subdomain": form_data.get("subdomain_granularity"),
"range": range_,
}

def query_obj(self):
qry = super(CalHeatmapViz, self).query_obj()
qry["metrics"] = [self.form_data["metric"]]
return qry


class NVD3Viz(BaseViz):

"""Base class for all nvd3 vizs"""
Expand Down Expand Up @@ -1572,6 +1634,7 @@ def get_data(self):
HeatmapViz,
BoxPlotViz,
TreemapViz,
CalHeatmapViz,
]

viz_types = OrderedDict([(v.viz_type, v) for v in viz_types_list
Expand Down
3 changes: 3 additions & 0 deletions docs/gallery.rst
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,6 @@ Gallery
.. image:: _static/img/viz_thumbnails/treemap.png
:scale: 25 %

.. image:: _static/img/viz_thumbnails/cal_heatmap.png
:scale: 25 %