mirror of https://github.com/apache/superset.git
Introducing Horizon charts (#472)
* Introducing Horizon charts * JS Lintin
This commit is contained in:
parent
1766f6edd6
commit
d1f0276408
Binary file not shown.
After Width: | Height: | Size: 100 KiB |
|
@ -28,7 +28,8 @@ var sourceMap = {
|
|||
word_cloud: 'word_cloud.js',
|
||||
world_map: 'world_map.js',
|
||||
treemap: 'treemap.js',
|
||||
cal_heatmap: 'cal_heatmap.js'
|
||||
cal_heatmap: 'cal_heatmap.js',
|
||||
horizon: 'horizon.js'
|
||||
};
|
||||
|
||||
var color = function () {
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
.horizon .slice_container div.horizon {
|
||||
border-bottom: solid 1px #444;
|
||||
border-top: 0px;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.horizon span {
|
||||
left: 5;
|
||||
position: absolute;
|
||||
color: black;
|
||||
text-shadow: 1px 1px rgba(255, 255, 255, 0.75);
|
||||
}
|
||||
|
||||
.horizon .slice_container {
|
||||
overflow: auto;
|
||||
}
|
|
@ -0,0 +1,237 @@
|
|||
// Copied and modified from
|
||||
// https://github.com/kmandov/d3-horizon-chart
|
||||
var d3 = require('d3');
|
||||
require('./horizon.css');
|
||||
|
||||
var horizonChart = function () {
|
||||
var colors = ["#313695", "#4575b4", "#74add1", "#abd9e9", "#fee090", "#fdae61", "#f46d43", "#d73027"];
|
||||
var bands = colors.length >> 1; // number of bands in each direction (positive / negative)
|
||||
var width = 1000;
|
||||
var height = 30;
|
||||
var offsetX = 0;
|
||||
var spacing = 0;
|
||||
var mode = 'offset';
|
||||
var axis = null;
|
||||
var title = null;
|
||||
var extent = null; // the extent is derived from the data, unless explicitly set via .extent([min, max])
|
||||
var x = null;
|
||||
var y = d3.scale.linear().range([0, height]);
|
||||
var canvas = null;
|
||||
|
||||
var b;
|
||||
|
||||
function my(data) {
|
||||
|
||||
var horizon = d3.select(this);
|
||||
var step = width / data.length;
|
||||
|
||||
horizon.append('span')
|
||||
.attr('class', 'title')
|
||||
.text(title);
|
||||
|
||||
horizon.append('span')
|
||||
.attr('class', 'value');
|
||||
|
||||
canvas = horizon.append('canvas');
|
||||
|
||||
canvas
|
||||
.attr('width', width)
|
||||
.attr('height', height);
|
||||
|
||||
var context = canvas.node().getContext('2d');
|
||||
context.imageSmoothingEnabled = false;
|
||||
|
||||
// update the y scale, based on the data extents
|
||||
var _extent = extent || d3.extent(data, function (d) { return d.y; });
|
||||
|
||||
var max = Math.max(-_extent[0], _extent[1]);
|
||||
y.domain([0, max]);
|
||||
|
||||
//x = d3.scaleTime().domain[];
|
||||
axis = d3.svg.axis(x).ticks(5);
|
||||
|
||||
context.clearRect(0, 0, width, height);
|
||||
//context.translate(0.5, 0.5);
|
||||
|
||||
// the data frame currently being shown:
|
||||
var startIndex = ~~ Math.max(0, -(offsetX / step));
|
||||
var endIndex = ~~ Math.min(data.length, startIndex + width / step);
|
||||
|
||||
// skip drawing if there's no data to be drawn
|
||||
if (startIndex > data.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// we are drawing positive & negative bands separately to avoid mutating canvas state
|
||||
// http://www.html5rocks.com/en/tutorials/canvas/performance/
|
||||
var negative = false;
|
||||
// draw positive bands
|
||||
var i, value, bExtents;
|
||||
for (b = 0; b < bands; b++) {
|
||||
context.fillStyle = colors[bands + b];
|
||||
|
||||
// Adjust the range based on the current band index.
|
||||
bExtents = (b + 1 - bands) * height;
|
||||
y.range([bands * height + bExtents, bExtents]);
|
||||
|
||||
// only the current data frame is being drawn i.e. what's visible:
|
||||
for (i = startIndex; i < endIndex; i++) {
|
||||
value = data[i].y;
|
||||
if (value <= 0) { negative = true; continue; }
|
||||
if (value === undefined) {
|
||||
continue;
|
||||
}
|
||||
context.fillRect(offsetX + i * step, y(value), step + 1, y(0) - y(value));
|
||||
}
|
||||
}
|
||||
|
||||
// draw negative bands
|
||||
if (negative) {
|
||||
|
||||
// mirror the negative bands, by flipping the canvas
|
||||
if (mode === 'offset') {
|
||||
context.translate(0, height);
|
||||
context.scale(1, -1);
|
||||
}
|
||||
|
||||
for (b = 0; b < bands; b++) {
|
||||
context.fillStyle = colors[bands - b - 1];
|
||||
|
||||
// Adjust the range based on the current band index.
|
||||
bExtents = (b + 1 - bands) * height;
|
||||
y.range([bands * height + bExtents, bExtents]);
|
||||
|
||||
// only the current data frame is being drawn i.e. what's visible:
|
||||
for (var ii = startIndex; ii < endIndex; ii++) {
|
||||
value = data[ii].y;
|
||||
if (value >= 0) {
|
||||
continue;
|
||||
}
|
||||
context.fillRect(offsetX + ii * step, y(-value), step + 1, y(0) - y(-value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
my.axis = function (_) {
|
||||
if (!arguments.length) { return axis; }
|
||||
axis = _;
|
||||
return my;
|
||||
};
|
||||
|
||||
my.title = function (_) {
|
||||
if (!arguments.length) { return title; }
|
||||
title = _;
|
||||
return my;
|
||||
};
|
||||
|
||||
my.canvas = function (_) {
|
||||
if (!arguments.length) { return canvas; }
|
||||
canvas = _;
|
||||
return my;
|
||||
};
|
||||
|
||||
// Array of colors representing the number of bands
|
||||
my.colors = function (_) {
|
||||
if (!arguments.length) {
|
||||
return colors;
|
||||
}
|
||||
colors = _;
|
||||
|
||||
// update the number of bands
|
||||
bands = colors.length >> 1;
|
||||
|
||||
return my;
|
||||
};
|
||||
|
||||
my.height = function (_) {
|
||||
if (!arguments.length) { return height; }
|
||||
height = _;
|
||||
return my;
|
||||
};
|
||||
|
||||
my.width = function (_) {
|
||||
if (!arguments.length) { return width; }
|
||||
width = _;
|
||||
return my;
|
||||
};
|
||||
|
||||
my.spacing = function (_) {
|
||||
if (!arguments.length) { return spacing; }
|
||||
spacing = _;
|
||||
return my;
|
||||
};
|
||||
|
||||
// mirror or offset
|
||||
my.mode = function (_) {
|
||||
if (!arguments.length) { return mode; }
|
||||
mode = _;
|
||||
return my;
|
||||
};
|
||||
|
||||
my.extent = function (_) {
|
||||
if (!arguments.length) { return extent; }
|
||||
extent = _;
|
||||
return my;
|
||||
};
|
||||
|
||||
my.offsetX = function (_) {
|
||||
if (!arguments.length) { return offsetX; }
|
||||
offsetX = _;
|
||||
return my;
|
||||
};
|
||||
|
||||
return my;
|
||||
};
|
||||
|
||||
function horizonViz(slice) {
|
||||
|
||||
function refresh() {
|
||||
d3.json(slice.jsonEndpoint(), function (error, payload) {
|
||||
var fd = payload.form_data;
|
||||
if (error) {
|
||||
slice.error(error.responseText);
|
||||
return '';
|
||||
}
|
||||
|
||||
var div = d3.select(slice.selector);
|
||||
div.selectAll('*').remove();
|
||||
var extent = null;
|
||||
if (fd.horizon_color_scale === 'overall') {
|
||||
var allValues = [];
|
||||
payload.data.forEach(function (d) {
|
||||
allValues = allValues.concat(d.values);
|
||||
});
|
||||
extent = d3.extent(allValues, function (d) { return d.y; });
|
||||
} else if (fd.horizon_color_scale === 'change') {
|
||||
payload.data.forEach(function (series) {
|
||||
var t0y = series.values[0].y; // value at time 0
|
||||
series.values.forEach(function (d, i) {
|
||||
d.y = d.y - t0y;
|
||||
});
|
||||
});
|
||||
}
|
||||
div.selectAll(".horizon")
|
||||
.data(payload.data)
|
||||
.enter()
|
||||
.append('div')
|
||||
.attr('class', 'horizon')
|
||||
.each(function (d, i) {
|
||||
horizonChart()
|
||||
.height(fd.series_height)
|
||||
.width(slice.width())
|
||||
.extent(extent)
|
||||
.title(d.key)
|
||||
.call(this, d.values, i);
|
||||
});
|
||||
|
||||
slice.done();
|
||||
});
|
||||
}
|
||||
return {
|
||||
render: refresh,
|
||||
resize: refresh
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = horizonViz;
|
|
@ -162,6 +162,11 @@ class FormFactory(object):
|
|||
"Color will be rendered based on a ratio "
|
||||
"of the cell against the sum of across this "
|
||||
"criteria")),
|
||||
'horizon_color_scale': SelectField(
|
||||
'Color Scale', choices=self.choicify([
|
||||
'series', 'overall', 'change']),
|
||||
default='series',
|
||||
description="Defines how the color are attributed."),
|
||||
'canvas_image_rendering': SelectField(
|
||||
'Rendering', choices=(
|
||||
('pixelated', 'pixelated (Sharp)'),
|
||||
|
@ -476,6 +481,11 @@ class FormFactory(object):
|
|||
default='smart_date',
|
||||
choices=TIMESTAMP_CHOICES,
|
||||
description="Timestamp Format"),
|
||||
'series_height': FreeFormSelectField(
|
||||
'Series Height',
|
||||
default=25,
|
||||
choices=self.choicify([10, 25, 40, 50, 75, 100, 150, 200]),
|
||||
description="Pixel height of each series"),
|
||||
'x_axis_format': FreeFormSelectField(
|
||||
'X axis format',
|
||||
default='smart_date',
|
||||
|
|
|
@ -1610,6 +1610,25 @@ class HeatmapViz(BaseViz):
|
|||
return df.to_dict(orient="records")
|
||||
|
||||
|
||||
class HorizonViz(NVD3TimeSeriesViz):
|
||||
|
||||
"""Horizon chart
|
||||
|
||||
https://www.npmjs.com/package/d3-horizon-chart
|
||||
"""
|
||||
|
||||
viz_type = "horizon"
|
||||
verbose_name = "Horizon Charts"
|
||||
credits = (
|
||||
'<a href="https://www.npmjs.com/package/d3-horizon-chart">'
|
||||
'd3-horizon-chart</a>')
|
||||
fieldsets = [NVD3TimeSeriesViz.fieldsets[0]] + [{
|
||||
'label': 'Chart Options',
|
||||
'fields': (
|
||||
('series_height', 'horizon_color_scale'),
|
||||
), }]
|
||||
|
||||
|
||||
viz_types_list = [
|
||||
TableViz,
|
||||
PivotTableViz,
|
||||
|
@ -1635,6 +1654,7 @@ viz_types_list = [
|
|||
BoxPlotViz,
|
||||
TreemapViz,
|
||||
CalHeatmapViz,
|
||||
HorizonViz,
|
||||
]
|
||||
|
||||
viz_types = OrderedDict([(v.viz_type, v) for v in viz_types_list
|
||||
|
|
|
@ -73,3 +73,6 @@ Gallery
|
|||
.. image:: _static/img/viz_thumbnails/cal_heatmap.png
|
||||
:scale: 25 %
|
||||
|
||||
.. image:: _static/img/viz_thumbnails/horizon.png
|
||||
:scale: 25 %
|
||||
|
||||
|
|
Loading…
Reference in New Issue