mirror of
https://github.com/apache/superset.git
synced 2024-09-17 19:19:38 -04:00
commit
5156d9f2dd
@ -110,6 +110,41 @@ class FormFactory(object):
|
|||||||
['stack', 'stream', 'expand']),
|
['stack', 'stream', 'expand']),
|
||||||
default='stack',
|
default='stack',
|
||||||
description=""),
|
description=""),
|
||||||
|
'linear_color_scheme': SelectField(
|
||||||
|
'Color Scheme', choices=self.choicify([
|
||||||
|
'fire', 'blue_white_yellow', 'white_black',
|
||||||
|
'black_white']),
|
||||||
|
default='fire',
|
||||||
|
description=""),
|
||||||
|
'normalize_across': SelectField(
|
||||||
|
'Normalize Across', choices=self.choicify([
|
||||||
|
'heatmap', 'x', 'y']),
|
||||||
|
default='heatmap',
|
||||||
|
description=(
|
||||||
|
"Color will be rendered based on a ratio "
|
||||||
|
"of the cell against the sum of across this "
|
||||||
|
"criteria")),
|
||||||
|
'canvas_image_rendering': SelectField(
|
||||||
|
'Rendering', choices=(
|
||||||
|
('pixelated', 'pixelated (Sharp)'),
|
||||||
|
('auto', 'auto (Smooth)'),
|
||||||
|
),
|
||||||
|
default='pixelated',
|
||||||
|
description=(
|
||||||
|
"image-rendering CSS attribute of the canvas object that "
|
||||||
|
"defines how the browser scales up the image")),
|
||||||
|
'xscale_interval': SelectField(
|
||||||
|
'XScale Interval', choices=self.choicify(range(1, 50)),
|
||||||
|
default='1',
|
||||||
|
description=(
|
||||||
|
"Number of step to take between ticks when "
|
||||||
|
"printing the x scale")),
|
||||||
|
'yscale_interval': SelectField(
|
||||||
|
'YScale Interval', choices=self.choicify(range(1, 50)),
|
||||||
|
default='1',
|
||||||
|
description=(
|
||||||
|
"Number of step to take between ticks when "
|
||||||
|
"printing the y scale")),
|
||||||
'bar_stacked': BetterBooleanField(
|
'bar_stacked': BetterBooleanField(
|
||||||
'Stacked Bars',
|
'Stacked Bars',
|
||||||
default=False,
|
default=False,
|
||||||
@ -142,6 +177,14 @@ class FormFactory(object):
|
|||||||
'Columns',
|
'Columns',
|
||||||
choices=self.choicify(datasource.column_names),
|
choices=self.choicify(datasource.column_names),
|
||||||
description="Columns to display"),
|
description="Columns to display"),
|
||||||
|
'all_columns_x': SelectField(
|
||||||
|
'X',
|
||||||
|
choices=self.choicify(datasource.column_names),
|
||||||
|
description="Columns to display"),
|
||||||
|
'all_columns_y': SelectField(
|
||||||
|
'Y',
|
||||||
|
choices=self.choicify(datasource.column_names),
|
||||||
|
description="Columns to display"),
|
||||||
'granularity': FreeFormSelectField(
|
'granularity': FreeFormSelectField(
|
||||||
'Time Granularity', default="one day",
|
'Time Granularity', default="one day",
|
||||||
choices=self.choicify([
|
choices=self.choicify([
|
||||||
|
55
panoramix/static/lib/d3.tip.css
Normal file
55
panoramix/static/lib/d3.tip.css
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
.d3-tip {
|
||||||
|
line-height: 1;
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 12px;
|
||||||
|
background: rgba(0, 0, 0, 0.8);
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 2px;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Creates a small triangle extender for the tooltip */
|
||||||
|
.d3-tip:after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: inline;
|
||||||
|
font-size: 10px;
|
||||||
|
width: 100%;
|
||||||
|
line-height: 1;
|
||||||
|
color: rgba(0, 0, 0, 0.8);
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Northward tooltips */
|
||||||
|
.d3-tip.n:after {
|
||||||
|
content: "\25BC";
|
||||||
|
margin: -1px 0 0 0;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Eastward tooltips */
|
||||||
|
.d3-tip.e:after {
|
||||||
|
content: "\25C0";
|
||||||
|
margin: -4px 0 0 0;
|
||||||
|
top: 50%;
|
||||||
|
left: -8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Southward tooltips */
|
||||||
|
.d3-tip.s:after {
|
||||||
|
content: "\25B2";
|
||||||
|
margin: 0 0 1px 0;
|
||||||
|
top: -8px;
|
||||||
|
left: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Westward tooltips */
|
||||||
|
.d3-tip.w:after {
|
||||||
|
content: "\25B6";
|
||||||
|
margin: -4px 0 0 -1px;
|
||||||
|
top: 50%;
|
||||||
|
left: 100%;
|
||||||
|
}
|
324
panoramix/static/lib/d3.tip.js
Normal file
324
panoramix/static/lib/d3.tip.js
Normal file
@ -0,0 +1,324 @@
|
|||||||
|
// d3.tip
|
||||||
|
// Copyright (c) 2013 Justin Palmer
|
||||||
|
//
|
||||||
|
// Tooltips for d3.js SVG visualizations
|
||||||
|
|
||||||
|
(function (root, factory) {
|
||||||
|
if (typeof define === 'function' && define.amd) {
|
||||||
|
// AMD. Register as an anonymous module with d3 as a dependency.
|
||||||
|
define(['d3'], factory)
|
||||||
|
} else if (typeof module === 'object' && module.exports) {
|
||||||
|
// CommonJS
|
||||||
|
module.exports = function(d3) {
|
||||||
|
d3.tip = factory(d3)
|
||||||
|
return d3.tip
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Browser global.
|
||||||
|
root.d3.tip = factory(root.d3)
|
||||||
|
}
|
||||||
|
}(this, function (d3) {
|
||||||
|
|
||||||
|
// Public - contructs a new tooltip
|
||||||
|
//
|
||||||
|
// Returns a tip
|
||||||
|
return function() {
|
||||||
|
var direction = d3_tip_direction,
|
||||||
|
offset = d3_tip_offset,
|
||||||
|
html = d3_tip_html,
|
||||||
|
node = initNode(),
|
||||||
|
svg = null,
|
||||||
|
point = null,
|
||||||
|
target = null
|
||||||
|
|
||||||
|
function tip(vis) {
|
||||||
|
svg = getSVGNode(vis)
|
||||||
|
point = svg.createSVGPoint()
|
||||||
|
document.body.appendChild(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public - show the tooltip on the screen
|
||||||
|
//
|
||||||
|
// Returns a tip
|
||||||
|
tip.show = function() {
|
||||||
|
var args = Array.prototype.slice.call(arguments)
|
||||||
|
if(args[args.length - 1] instanceof SVGElement) target = args.pop()
|
||||||
|
|
||||||
|
var content = html.apply(this, args),
|
||||||
|
poffset = offset.apply(this, args),
|
||||||
|
dir = direction.apply(this, args),
|
||||||
|
nodel = getNodeEl(),
|
||||||
|
i = directions.length,
|
||||||
|
coords,
|
||||||
|
scrollTop = document.documentElement.scrollTop || document.body.scrollTop,
|
||||||
|
scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft
|
||||||
|
|
||||||
|
nodel.html(content)
|
||||||
|
.style({ opacity: 1, 'pointer-events': 'all' })
|
||||||
|
|
||||||
|
while(i--) nodel.classed(directions[i], false)
|
||||||
|
coords = direction_callbacks.get(dir).apply(this)
|
||||||
|
nodel.classed(dir, true).style({
|
||||||
|
top: (coords.top + poffset[0]) + scrollTop + 'px',
|
||||||
|
left: (coords.left + poffset[1]) + scrollLeft + 'px'
|
||||||
|
})
|
||||||
|
|
||||||
|
return tip
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public - hide the tooltip
|
||||||
|
//
|
||||||
|
// Returns a tip
|
||||||
|
tip.hide = function() {
|
||||||
|
var nodel = getNodeEl()
|
||||||
|
nodel.style({ opacity: 0, 'pointer-events': 'none' })
|
||||||
|
return tip
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public: Proxy attr calls to the d3 tip container. Sets or gets attribute value.
|
||||||
|
//
|
||||||
|
// n - name of the attribute
|
||||||
|
// v - value of the attribute
|
||||||
|
//
|
||||||
|
// Returns tip or attribute value
|
||||||
|
tip.attr = function(n, v) {
|
||||||
|
if (arguments.length < 2 && typeof n === 'string') {
|
||||||
|
return getNodeEl().attr(n)
|
||||||
|
} else {
|
||||||
|
var args = Array.prototype.slice.call(arguments)
|
||||||
|
d3.selection.prototype.attr.apply(getNodeEl(), args)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tip
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public: Proxy style calls to the d3 tip container. Sets or gets a style value.
|
||||||
|
//
|
||||||
|
// n - name of the property
|
||||||
|
// v - value of the property
|
||||||
|
//
|
||||||
|
// Returns tip or style property value
|
||||||
|
tip.style = function(n, v) {
|
||||||
|
if (arguments.length < 2 && typeof n === 'string') {
|
||||||
|
return getNodeEl().style(n)
|
||||||
|
} else {
|
||||||
|
var args = Array.prototype.slice.call(arguments)
|
||||||
|
d3.selection.prototype.style.apply(getNodeEl(), args)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tip
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public: Set or get the direction of the tooltip
|
||||||
|
//
|
||||||
|
// v - One of n(north), s(south), e(east), or w(west), nw(northwest),
|
||||||
|
// sw(southwest), ne(northeast) or se(southeast)
|
||||||
|
//
|
||||||
|
// Returns tip or direction
|
||||||
|
tip.direction = function(v) {
|
||||||
|
if (!arguments.length) return direction
|
||||||
|
direction = v == null ? v : d3.functor(v)
|
||||||
|
|
||||||
|
return tip
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public: Sets or gets the offset of the tip
|
||||||
|
//
|
||||||
|
// v - Array of [x, y] offset
|
||||||
|
//
|
||||||
|
// Returns offset or
|
||||||
|
tip.offset = function(v) {
|
||||||
|
if (!arguments.length) return offset
|
||||||
|
offset = v == null ? v : d3.functor(v)
|
||||||
|
|
||||||
|
return tip
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public: sets or gets the html value of the tooltip
|
||||||
|
//
|
||||||
|
// v - String value of the tip
|
||||||
|
//
|
||||||
|
// Returns html value or tip
|
||||||
|
tip.html = function(v) {
|
||||||
|
if (!arguments.length) return html
|
||||||
|
html = v == null ? v : d3.functor(v)
|
||||||
|
|
||||||
|
return tip
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public: destroys the tooltip and removes it from the DOM
|
||||||
|
//
|
||||||
|
// Returns a tip
|
||||||
|
tip.destroy = function() {
|
||||||
|
if(node) {
|
||||||
|
getNodeEl().remove();
|
||||||
|
node = null;
|
||||||
|
}
|
||||||
|
return tip;
|
||||||
|
}
|
||||||
|
|
||||||
|
function d3_tip_direction() { return 'n' }
|
||||||
|
function d3_tip_offset() { return [0, 0] }
|
||||||
|
function d3_tip_html() { return ' ' }
|
||||||
|
|
||||||
|
var direction_callbacks = d3.map({
|
||||||
|
n: direction_n,
|
||||||
|
s: direction_s,
|
||||||
|
e: direction_e,
|
||||||
|
w: direction_w,
|
||||||
|
nw: direction_nw,
|
||||||
|
ne: direction_ne,
|
||||||
|
sw: direction_sw,
|
||||||
|
se: direction_se
|
||||||
|
}),
|
||||||
|
|
||||||
|
directions = direction_callbacks.keys()
|
||||||
|
|
||||||
|
function direction_n() {
|
||||||
|
var bbox = getScreenBBox()
|
||||||
|
return {
|
||||||
|
top: bbox.n.y - node.offsetHeight,
|
||||||
|
left: bbox.n.x - node.offsetWidth / 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function direction_s() {
|
||||||
|
var bbox = getScreenBBox()
|
||||||
|
return {
|
||||||
|
top: bbox.s.y,
|
||||||
|
left: bbox.s.x - node.offsetWidth / 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function direction_e() {
|
||||||
|
var bbox = getScreenBBox()
|
||||||
|
return {
|
||||||
|
top: bbox.e.y - node.offsetHeight / 2,
|
||||||
|
left: bbox.e.x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function direction_w() {
|
||||||
|
var bbox = getScreenBBox()
|
||||||
|
return {
|
||||||
|
top: bbox.w.y - node.offsetHeight / 2,
|
||||||
|
left: bbox.w.x - node.offsetWidth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function direction_nw() {
|
||||||
|
var bbox = getScreenBBox()
|
||||||
|
return {
|
||||||
|
top: bbox.nw.y - node.offsetHeight,
|
||||||
|
left: bbox.nw.x - node.offsetWidth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function direction_ne() {
|
||||||
|
var bbox = getScreenBBox()
|
||||||
|
return {
|
||||||
|
top: bbox.ne.y - node.offsetHeight,
|
||||||
|
left: bbox.ne.x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function direction_sw() {
|
||||||
|
var bbox = getScreenBBox()
|
||||||
|
return {
|
||||||
|
top: bbox.sw.y,
|
||||||
|
left: bbox.sw.x - node.offsetWidth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function direction_se() {
|
||||||
|
var bbox = getScreenBBox()
|
||||||
|
return {
|
||||||
|
top: bbox.se.y,
|
||||||
|
left: bbox.e.x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initNode() {
|
||||||
|
var node = d3.select(document.createElement('div'))
|
||||||
|
node.style({
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
opacity: 0,
|
||||||
|
'pointer-events': 'none',
|
||||||
|
'box-sizing': 'border-box'
|
||||||
|
})
|
||||||
|
|
||||||
|
return node.node()
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSVGNode(el) {
|
||||||
|
el = el.node()
|
||||||
|
if(el.tagName.toLowerCase() === 'svg')
|
||||||
|
return el
|
||||||
|
|
||||||
|
return el.ownerSVGElement
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNodeEl() {
|
||||||
|
if(node === null) {
|
||||||
|
node = initNode();
|
||||||
|
// re-add node to DOM
|
||||||
|
document.body.appendChild(node);
|
||||||
|
};
|
||||||
|
return d3.select(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private - gets the screen coordinates of a shape
|
||||||
|
//
|
||||||
|
// Given a shape on the screen, will return an SVGPoint for the directions
|
||||||
|
// n(north), s(south), e(east), w(west), ne(northeast), se(southeast), nw(northwest),
|
||||||
|
// sw(southwest).
|
||||||
|
//
|
||||||
|
// +-+-+
|
||||||
|
// | |
|
||||||
|
// + +
|
||||||
|
// | |
|
||||||
|
// +-+-+
|
||||||
|
//
|
||||||
|
// Returns an Object {n, s, e, w, nw, sw, ne, se}
|
||||||
|
function getScreenBBox() {
|
||||||
|
var targetel = target || d3.event.target;
|
||||||
|
|
||||||
|
while ('undefined' === typeof targetel.getScreenCTM && 'undefined' === targetel.parentNode) {
|
||||||
|
targetel = targetel.parentNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bbox = {},
|
||||||
|
matrix = targetel.getScreenCTM(),
|
||||||
|
tbbox = targetel.getBBox(),
|
||||||
|
width = tbbox.width,
|
||||||
|
height = tbbox.height,
|
||||||
|
x = tbbox.x,
|
||||||
|
y = tbbox.y
|
||||||
|
|
||||||
|
point.x = x
|
||||||
|
point.y = y
|
||||||
|
bbox.nw = point.matrixTransform(matrix)
|
||||||
|
point.x += width
|
||||||
|
bbox.ne = point.matrixTransform(matrix)
|
||||||
|
point.y += height
|
||||||
|
bbox.se = point.matrixTransform(matrix)
|
||||||
|
point.x -= width
|
||||||
|
bbox.sw = point.matrixTransform(matrix)
|
||||||
|
point.y -= height / 2
|
||||||
|
bbox.w = point.matrixTransform(matrix)
|
||||||
|
point.x += width
|
||||||
|
bbox.e = point.matrixTransform(matrix)
|
||||||
|
point.x -= width / 2
|
||||||
|
point.y -= height / 2
|
||||||
|
bbox.n = point.matrixTransform(matrix)
|
||||||
|
point.y += height
|
||||||
|
bbox.s = point.matrixTransform(matrix)
|
||||||
|
|
||||||
|
return bbox
|
||||||
|
}
|
||||||
|
|
||||||
|
return tip
|
||||||
|
};
|
||||||
|
|
||||||
|
}));
|
@ -1,23 +1,54 @@
|
|||||||
var px = (function() {
|
var color = function(){
|
||||||
|
// Color related utility functions go in this object
|
||||||
var visualizations = {};
|
|
||||||
var dashboard = undefined;
|
|
||||||
|
|
||||||
var bnbColors = [
|
var bnbColors = [
|
||||||
//rausch hackb kazan babu lima beach barol
|
//rausch hackb kazan babu lima beach barol
|
||||||
'#ff5a5f', '#7b0051', '#007A87', '#00d1c1', '#8ce071', '#ffb400', '#b4a76c',
|
'#ff5a5f', '#7b0051', '#007A87', '#00d1c1', '#8ce071', '#ffb400', '#b4a76c',
|
||||||
'#ff8083', '#cc0086', '#00a1b3', '#00ffeb', '#bbedab', '#ffd266', '#cbc29a',
|
'#ff8083', '#cc0086', '#00a1b3', '#00ffeb', '#bbedab', '#ffd266', '#cbc29a',
|
||||||
'#ff3339', '#ff1ab1', '#005c66', '#00b3a5', '#55d12e', '#b37e00', '#988b4e',
|
'#ff3339', '#ff1ab1', '#005c66', '#00b3a5', '#55d12e', '#b37e00', '#988b4e',
|
||||||
];
|
];
|
||||||
function colorBnb() {
|
var spectrums = {
|
||||||
|
'fire': ['white', 'yellow', 'red', 'black'],
|
||||||
|
'blue_white_yellow': ['#00d1c1', 'white', '#ffb400'],
|
||||||
|
'white_black': ['white', 'black'],
|
||||||
|
'black_white': ['black', 'white'],
|
||||||
|
}
|
||||||
|
var colorBnb = function() {
|
||||||
// Color factory
|
// Color factory
|
||||||
var seen = {};
|
var seen = {};
|
||||||
return function(s){
|
return function(s){
|
||||||
if(seen[s] === undefined)
|
if(seen[s] === undefined)
|
||||||
seen[s] = Object.keys(seen).length;
|
seen[s] = Object.keys(seen).length;
|
||||||
return bnbColors[seen[s] % bnbColors.length];
|
return this.bnbColors[seen[s] % this.bnbColors.length];
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
colorScalerFactory = function (colors, data, accessor){
|
||||||
|
// Returns a linear scaler our of an array of color
|
||||||
|
if(!Array.isArray(colors))
|
||||||
|
colors = spectrums[colors];
|
||||||
|
if(data !== undefined)
|
||||||
|
var ext = d3.extent(data, accessor);
|
||||||
|
else
|
||||||
|
var ext = [0,1];
|
||||||
|
|
||||||
|
var points = [];
|
||||||
|
var chunkSize = (ext[1] - ext[0]) / colors.length;
|
||||||
|
$.each(colors, function(i, c){
|
||||||
|
points.push(i * chunkSize)
|
||||||
|
});
|
||||||
|
return d3.scale.linear().domain(points).range(colors);
|
||||||
}
|
}
|
||||||
|
return {
|
||||||
|
bnbColors: bnbColors,
|
||||||
|
category21: colorBnb(),
|
||||||
|
colorScalerFactory: colorScalerFactory,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var px = (function() {
|
||||||
|
|
||||||
|
var visualizations = {};
|
||||||
|
var dashboard = undefined;
|
||||||
|
|
||||||
|
|
||||||
function UTC(dttm){
|
function UTC(dttm){
|
||||||
return v = new Date(dttm.getUTCFullYear(), dttm.getUTCMonth(), dttm.getUTCDate(), dttm.getUTCHours(), dttm.getUTCMinutes(), dttm.getUTCSeconds());
|
return v = new Date(dttm.getUTCFullYear(), dttm.getUTCMonth(), dttm.getUTCDate(), dttm.getUTCHours(), dttm.getUTCMinutes(), dttm.getUTCSeconds());
|
||||||
@ -488,8 +519,6 @@ var px = (function() {
|
|||||||
initDashboardView: initDashboardView,
|
initDashboardView: initDashboardView,
|
||||||
formatDate: formatDate,
|
formatDate: formatDate,
|
||||||
timeFormatFactory: timeFormatFactory,
|
timeFormatFactory: timeFormatFactory,
|
||||||
colorBnb: colorBnb,
|
color: color(),
|
||||||
bnbColors: bnbColors,
|
|
||||||
color: colorBnb(),
|
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
22
panoramix/static/widgets/viz_heatmap.css
Normal file
22
panoramix/static/widgets/viz_heatmap.css
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
.heatmap .axis text {
|
||||||
|
font: 10px sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heatmap .axis path,
|
||||||
|
.heatmap .axis line {
|
||||||
|
fill: none;
|
||||||
|
stroke: #000;
|
||||||
|
shape-rendering: crispEdges;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heatmap svg {
|
||||||
|
}
|
||||||
|
|
||||||
|
.heatmap canvas, .heatmap img {
|
||||||
|
image-rendering: optimizeSpeed; /* Older versions of FF */
|
||||||
|
image-rendering: -moz-crisp-edges; /* FF 6.0+ */
|
||||||
|
image-rendering: -webkit-optimize-contrast; /* Safari */
|
||||||
|
image-rendering: -o-crisp-edges; /* OS X & Windows Opera (12.02+) */
|
||||||
|
image-rendering: pixelated; /* Awesome future-browsers */
|
||||||
|
-ms-interpolation-mode: nearest-neighbor; /* IE */
|
||||||
|
}
|
184
panoramix/static/widgets/viz_heatmap.js
Normal file
184
panoramix/static/widgets/viz_heatmap.js
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
// Inspired from http://bl.ocks.org/mbostock/3074470
|
||||||
|
// https://jsfiddle.net/cyril123/h0reyumq/
|
||||||
|
px.registerViz('heatmap', function(slice) {
|
||||||
|
var margins = {t:0, r:0, b:50, l:50};
|
||||||
|
function refresh() {
|
||||||
|
var width = slice.width();
|
||||||
|
var height = slice.height();
|
||||||
|
var hmWidth = width - (margins.l + margins.r)
|
||||||
|
var hmHeight = height - (margins.b + margins.t)
|
||||||
|
var fp = d3.format('.3p');
|
||||||
|
d3.json(slice.jsonEndpoint(), function(error, payload) {
|
||||||
|
var matrix = {};
|
||||||
|
if (error){
|
||||||
|
slice.error(error.responseText);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
var fd = payload.form_data;
|
||||||
|
var data = payload.data;
|
||||||
|
function ordScale(k, rangeBands, reverse) {
|
||||||
|
if (reverse === undefined)
|
||||||
|
reverse = false;
|
||||||
|
domain = {};
|
||||||
|
$.each(data, function(i, d){
|
||||||
|
domain[d[k]] = true;
|
||||||
|
});
|
||||||
|
domain = Object.keys(domain).sort();
|
||||||
|
if (reverse)
|
||||||
|
domain.reverse();
|
||||||
|
if (rangeBands === undefined) {
|
||||||
|
return d3.scale.ordinal().domain(domain).range(d3.range(domain.length));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return d3.scale.ordinal().domain(domain).rangeBands(rangeBands);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var xScale = ordScale('x');
|
||||||
|
var yScale = ordScale('y', undefined, true);
|
||||||
|
var xRbScale = ordScale('x', [0, hmWidth]);
|
||||||
|
var yRbScale = ordScale('y', [hmHeight, 0]);
|
||||||
|
var X = 0, Y = 1;
|
||||||
|
var heatmapDim = [xRbScale.domain().length, yRbScale.domain().length];
|
||||||
|
|
||||||
|
var color = px.color.colorScalerFactory(fd.linear_color_scheme);
|
||||||
|
|
||||||
|
var scale = [
|
||||||
|
d3.scale.linear()
|
||||||
|
.domain([0, heatmapDim[X]])
|
||||||
|
.range([0, hmWidth]),
|
||||||
|
d3.scale.linear()
|
||||||
|
.domain([0, heatmapDim[Y]])
|
||||||
|
.range([0, hmHeight])
|
||||||
|
];
|
||||||
|
|
||||||
|
var container = d3.select(slice.selector)
|
||||||
|
.style("left", "0px")
|
||||||
|
.style("position", "relative")
|
||||||
|
.style("top", "0px");
|
||||||
|
|
||||||
|
var canvas = container.append("canvas")
|
||||||
|
.attr("width", heatmapDim[X])
|
||||||
|
.attr("height", heatmapDim[Y])
|
||||||
|
.style("width", hmWidth + "px")
|
||||||
|
.style("height", hmHeight + "px")
|
||||||
|
.style("image-rendering", fd.canvas_image_rendering)
|
||||||
|
.style("left", margins.l + "px")
|
||||||
|
.style("top", margins.t + "px")
|
||||||
|
.style("position", "absolute");
|
||||||
|
|
||||||
|
var svg = container.append("svg")
|
||||||
|
.attr("width", width)
|
||||||
|
.attr("height", height)
|
||||||
|
.style("left", "0px")
|
||||||
|
.style("top", "0px")
|
||||||
|
.style("position", "absolute");
|
||||||
|
|
||||||
|
var rect = svg.append('g')
|
||||||
|
.attr("transform", "translate(" + margins.l + "," + margins.t + ")")
|
||||||
|
.append('rect')
|
||||||
|
.style('fill-opacity', 0)
|
||||||
|
.attr('stroke', 'black')
|
||||||
|
.attr("width", hmWidth)
|
||||||
|
.attr("height", hmHeight);
|
||||||
|
|
||||||
|
var tip = d3.tip()
|
||||||
|
.attr('class', 'd3-tip')
|
||||||
|
.offset(function(){
|
||||||
|
var k = d3.mouse(this);
|
||||||
|
var x = k[0] - (hmWidth/ 2);
|
||||||
|
return [k[1] - 20, x];
|
||||||
|
})
|
||||||
|
.html(function (d) {
|
||||||
|
var k = d3.mouse(this);
|
||||||
|
var m = Math.floor(scale[0].invert(k[0]));
|
||||||
|
var n = Math.floor(scale[1].invert(k[1]));
|
||||||
|
if(m in matrix && n in matrix[m]) {
|
||||||
|
var obj = matrix[m][n];
|
||||||
|
var s = "";
|
||||||
|
s += "<div><b>" + fd.all_columns_x + ": </b>" + obj.x + "<div>"
|
||||||
|
s += "<div><b>" + fd.all_columns_y +": </b>" + obj.y + "<div>"
|
||||||
|
s += "<div><b>" + fd.metric + ": </b>" + obj.v + "<div>"
|
||||||
|
s += "<div><b>%: </b>" + fp(obj.perc) + "<div>"
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
rect.call(tip);
|
||||||
|
var xscale_skip = 2;
|
||||||
|
var yscale_skip = 2;
|
||||||
|
|
||||||
|
xAxis = d3.svg.axis()
|
||||||
|
.scale(xRbScale)
|
||||||
|
.tickValues(xRbScale.domain().filter(
|
||||||
|
function(d, i) { return !(i % (parseInt(fd.xscale_interval))); }))
|
||||||
|
.orient("bottom");
|
||||||
|
yAxis = d3.svg.axis()
|
||||||
|
.scale(yRbScale)
|
||||||
|
.tickValues(yRbScale.domain().filter(
|
||||||
|
function(d, i) { return !(i % (parseInt(fd.yscale_interval))); }))
|
||||||
|
.orient("left");
|
||||||
|
|
||||||
|
svg.append("g")
|
||||||
|
.attr("class", "x axis")
|
||||||
|
.attr("transform", "translate(" + margins.l + "," + (margins.t + hmHeight) + ")")
|
||||||
|
.call(xAxis)
|
||||||
|
.selectAll("text")
|
||||||
|
.style("text-anchor", "end")
|
||||||
|
.attr("transform", "rotate(-45)")
|
||||||
|
.style("font-weight", "bold");
|
||||||
|
svg.append("g")
|
||||||
|
.attr("class", "y axis")
|
||||||
|
.attr("transform", "translate(" + margins.l + ", 0)")
|
||||||
|
.call(yAxis);
|
||||||
|
|
||||||
|
rect.on('mousemove', tip.show);
|
||||||
|
rect.on('mouseout', tip.hide);
|
||||||
|
|
||||||
|
var context = canvas.node().getContext("2d");
|
||||||
|
context.imageSmoothingEnabled = false;
|
||||||
|
var imageObj;
|
||||||
|
var imageDim;
|
||||||
|
var imageScale;
|
||||||
|
createImageObj();
|
||||||
|
|
||||||
|
// Compute the pixel colors; scaled by CSS.
|
||||||
|
function createImageObj() {
|
||||||
|
imageObj = new Image();
|
||||||
|
image = context.createImageData(heatmapDim[0], heatmapDim[1]);
|
||||||
|
var pixs = {};
|
||||||
|
$.each(data, function(i, d) {
|
||||||
|
var c = d3.rgb(color(d.perc));
|
||||||
|
var x = xScale(d.x);
|
||||||
|
var y = yScale(d.y);
|
||||||
|
pixs[x + (y*xScale.domain().length)] = c;
|
||||||
|
if (matrix[x] === undefined)
|
||||||
|
matrix[x] = {}
|
||||||
|
if (matrix[x][y] === undefined)
|
||||||
|
matrix[x][y] = d;
|
||||||
|
});
|
||||||
|
|
||||||
|
p = -1;
|
||||||
|
for(var i=0; i< heatmapDim[0] * heatmapDim[1]; i++){
|
||||||
|
c = pixs[i];
|
||||||
|
var alpha = 255;
|
||||||
|
if (c === undefined){
|
||||||
|
c = d3.rgb('#F00');
|
||||||
|
alpha = 0;
|
||||||
|
}
|
||||||
|
image.data[++p] = c.r;
|
||||||
|
image.data[++p] = c.g;
|
||||||
|
image.data[++p] = c.b;
|
||||||
|
image.data[++p] = alpha;
|
||||||
|
}
|
||||||
|
context.putImageData(image, 0, 0);
|
||||||
|
imageObj.src = canvas.node().toDataURL();
|
||||||
|
}
|
||||||
|
slice.done();
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
render: refresh,
|
||||||
|
resize: refresh,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
@ -147,7 +147,7 @@ function viz_nvd3(slice) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
chart.color(function(d, i){
|
chart.color(function(d, i){
|
||||||
return px.color(d[colorKey]);
|
return px.color.category21(d[colorKey]);
|
||||||
});
|
});
|
||||||
d3.select(slice.selector).append("svg")
|
d3.select(slice.selector).append("svg")
|
||||||
.datum(payload.data)
|
.datum(payload.data)
|
||||||
|
@ -49,7 +49,7 @@ px.registerViz('word_cloud', function(slice) {
|
|||||||
.enter().append("text")
|
.enter().append("text")
|
||||||
.style("font-size", function(d) { return d.size + "px"; })
|
.style("font-size", function(d) { return d.size + "px"; })
|
||||||
.style("font-family", "Impact")
|
.style("font-family", "Impact")
|
||||||
.style("fill", function(d, i) {return px.color(d.text); })
|
.style("fill", function(d, i) {return px.color.category21(d.text); })
|
||||||
.attr("text-anchor", "middle")
|
.attr("text-anchor", "middle")
|
||||||
.attr("transform", function(d) {
|
.attr("transform", function(d) {
|
||||||
return "translate(" + [d.x, d.y] + ") rotate(" + d.rotate + ")";
|
return "translate(" + [d.x, d.y] + ") rotate(" + d.rotate + ")";
|
||||||
|
@ -1190,6 +1190,69 @@ class ParallelCoordinatesViz(BaseViz):
|
|||||||
df = df[[self.form_data.get('series')] + self.form_data.get('metrics')]
|
df = df[[self.form_data.get('series')] + self.form_data.get('metrics')]
|
||||||
return df.to_json(orient="records")
|
return df.to_json(orient="records")
|
||||||
|
|
||||||
|
class HeatmapViz(BaseViz):
|
||||||
|
viz_type = "heatmap"
|
||||||
|
verbose_name = "Heatmap"
|
||||||
|
is_timeseries = False
|
||||||
|
js_files = ['lib/d3.tip.js', 'widgets/viz_heatmap.js']
|
||||||
|
css_files = ['lib/d3.tip.css', 'widgets/viz_heatmap.css']
|
||||||
|
fieldsets = (
|
||||||
|
{
|
||||||
|
'label': None,
|
||||||
|
'fields': (
|
||||||
|
'granularity',
|
||||||
|
('since', 'until'),
|
||||||
|
'all_columns_x',
|
||||||
|
'all_columns_y',
|
||||||
|
'metric',
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'label': 'Heatmap Options',
|
||||||
|
'fields': (
|
||||||
|
'linear_color_scheme',
|
||||||
|
('xscale_interval', 'yscale_interval'),
|
||||||
|
'canvas_image_rendering',
|
||||||
|
'normalize_across',
|
||||||
|
)
|
||||||
|
},)
|
||||||
|
def query_obj(self):
|
||||||
|
d = super(HeatmapViz, self).query_obj()
|
||||||
|
fd = self.form_data
|
||||||
|
d['metrics'] = [fd.get('metric')]
|
||||||
|
d['groupby'] = [fd.get('all_columns_x'), fd.get('all_columns_y')]
|
||||||
|
return d
|
||||||
|
|
||||||
|
def get_json_data(self):
|
||||||
|
df = self.get_df()
|
||||||
|
fd = self.form_data
|
||||||
|
x = fd.get('all_columns_x')
|
||||||
|
y = fd.get('all_columns_y')
|
||||||
|
v = fd.get('metric')
|
||||||
|
if x == y:
|
||||||
|
df.columns = ['x', 'y', 'v']
|
||||||
|
else:
|
||||||
|
df = df[[x, y, v]]
|
||||||
|
df.columns = ['x', 'y', 'v']
|
||||||
|
norm = fd.get('normalize_across')
|
||||||
|
overall = False
|
||||||
|
if norm == 'heatmap':
|
||||||
|
overall = True
|
||||||
|
else:
|
||||||
|
gb = df.groupby(norm, group_keys=False)
|
||||||
|
if len(gb) <= 1:
|
||||||
|
overall = True
|
||||||
|
else:
|
||||||
|
df['perc'] = (
|
||||||
|
gb.apply(
|
||||||
|
lambda x: (x.v - x.v.min()) / (x.v.max() - x.v.min()))
|
||||||
|
)
|
||||||
|
if overall:
|
||||||
|
v = df.v
|
||||||
|
min_ = v.min()
|
||||||
|
df['perc'] = (v - min_) / (v.max() - min_)
|
||||||
|
return df.to_json(orient="records")
|
||||||
|
|
||||||
|
|
||||||
viz_types_list = [
|
viz_types_list = [
|
||||||
TableViz,
|
TableViz,
|
||||||
@ -1211,6 +1274,7 @@ viz_types_list = [
|
|||||||
FilterBoxViz,
|
FilterBoxViz,
|
||||||
IFrameViz,
|
IFrameViz,
|
||||||
ParallelCoordinatesViz,
|
ParallelCoordinatesViz,
|
||||||
|
HeatmapViz,
|
||||||
]
|
]
|
||||||
|
|
||||||
viz_types = OrderedDict([(v.viz_type, v) for v in viz_types_list])
|
viz_types = OrderedDict([(v.viz_type, v) for v in viz_types_list])
|
||||||
|
Loading…
Reference in New Issue
Block a user