mirror of
https://github.com/getredash/redash.git
synced 2026-03-21 16:00:09 -04:00
187 lines
5.3 KiB
JavaScript
187 lines
5.3 KiB
JavaScript
import d3 from 'd3';
|
|
import box from './d3box';
|
|
import editorTemplate from './box-plot-editor.html';
|
|
|
|
function boxPlotRenderer() {
|
|
return {
|
|
restrict: 'E',
|
|
template: '<div></div>',
|
|
link($scope, elm) {
|
|
function calcIqr(k) {
|
|
return (d) => {
|
|
const q1 = d.quartiles[0];
|
|
const q3 = d.quartiles[2];
|
|
const iqr = (q3 - q1) * k;
|
|
|
|
let i = -1;
|
|
let j = d.length;
|
|
|
|
// eslint-disable-next-line no-plusplus
|
|
while (d[++i] < q1 - iqr);
|
|
// eslint-disable-next-line no-plusplus
|
|
while (d[--j] > q3 + iqr);
|
|
return [i, j];
|
|
};
|
|
}
|
|
|
|
$scope.$watch('[queryResult && queryResult.getData(), visualization.options]', () => {
|
|
if ($scope.queryResult.getData() === null) {
|
|
return;
|
|
}
|
|
|
|
const data = $scope.queryResult.getData();
|
|
const parentWidth = d3.select(elm[0].parentNode).node().getBoundingClientRect().width;
|
|
const margin = {
|
|
top: 10, right: 50, bottom: 40, left: 50, inner: 25,
|
|
};
|
|
const width = parentWidth - margin.right - margin.left;
|
|
const height = 500 - margin.top - margin.bottom;
|
|
|
|
let min = Infinity;
|
|
let max = -Infinity;
|
|
const mydata = [];
|
|
let value = 0;
|
|
let d = [];
|
|
const xAxisLabel = $scope.visualization.options.xAxisLabel;
|
|
const yAxisLabel = $scope.visualization.options.yAxisLabel;
|
|
|
|
const columns = $scope.queryResult.getColumnNames();
|
|
const xscale = d3.scale.ordinal()
|
|
.domain(columns)
|
|
.rangeBands([0, parentWidth - margin.left - margin.right]);
|
|
|
|
let boxWidth;
|
|
if (columns.length > 1) {
|
|
boxWidth = Math.min(xscale(columns[1]), 120.0);
|
|
} else {
|
|
boxWidth = 120.0;
|
|
}
|
|
margin.inner = boxWidth / 3.0;
|
|
|
|
columns.forEach((column, i) => {
|
|
d = mydata[i] = [];
|
|
data.forEach((row) => {
|
|
value = row[column];
|
|
d.push(value);
|
|
if (value > max) max = Math.ceil(value);
|
|
if (value < min) min = Math.floor(value);
|
|
});
|
|
});
|
|
|
|
const yscale = d3.scale.linear()
|
|
.domain([min * 0.99, max * 1.01])
|
|
.range([height, 0]);
|
|
|
|
const chart = box()
|
|
.whiskers(calcIqr(1.5))
|
|
.width(boxWidth - 2 * margin.inner)
|
|
.height(height)
|
|
.domain([min * 0.99, max * 1.01]);
|
|
const xAxis = d3.svg.axis()
|
|
.scale(xscale)
|
|
.orient('bottom');
|
|
|
|
|
|
const yAxis = d3.svg.axis()
|
|
.scale(yscale)
|
|
.orient('left');
|
|
|
|
const xLines = d3.svg.axis()
|
|
.scale(xscale)
|
|
.tickSize(height)
|
|
.orient('bottom');
|
|
|
|
const yLines = d3.svg.axis()
|
|
.scale(yscale)
|
|
.tickSize(width)
|
|
.orient('right');
|
|
|
|
function barOffset(i) {
|
|
return xscale(columns[i]) + (xscale(columns[1]) - margin.inner) / 2.0;
|
|
}
|
|
|
|
d3.select(elm[0]).selectAll('svg').remove();
|
|
|
|
const plot = d3.select(elm[0])
|
|
.append('svg')
|
|
.attr('width', parentWidth)
|
|
.attr('height', height + margin.bottom + margin.top)
|
|
.append('g')
|
|
.attr('width', parentWidth - margin.left - margin.right)
|
|
.attr('transform', `translate(${margin.left},${margin.top})`);
|
|
|
|
d3.select('svg').append('text')
|
|
.attr('class', 'box')
|
|
.attr('x', parentWidth / 2.0)
|
|
.attr('text-anchor', 'middle')
|
|
.attr('y', height + margin.bottom)
|
|
.text(xAxisLabel);
|
|
|
|
d3.select('svg').append('text')
|
|
.attr('class', 'box')
|
|
.attr('transform', `translate(10,${(height + margin.top + margin.bottom) / 2.0})rotate(-90)`)
|
|
.attr('text-anchor', 'middle')
|
|
.text(yAxisLabel);
|
|
|
|
plot.append('rect')
|
|
.attr('class', 'grid-background')
|
|
.attr('width', width)
|
|
.attr('height', height);
|
|
|
|
plot.append('g')
|
|
.attr('class', 'grid')
|
|
.call(yLines);
|
|
|
|
plot.append('g')
|
|
.attr('class', 'grid')
|
|
.call(xLines);
|
|
|
|
plot.append('g')
|
|
.attr('class', 'x axis')
|
|
.attr('transform', `translate(0,${height})`)
|
|
.call(xAxis);
|
|
|
|
plot.append('g')
|
|
.attr('class', 'y axis')
|
|
.call(yAxis);
|
|
|
|
plot.selectAll('.box').data(mydata)
|
|
.enter().append('g')
|
|
.attr('class', 'box')
|
|
.attr('width', boxWidth)
|
|
.attr('height', height)
|
|
.attr('transform', (_, i) => `translate(${barOffset(i)},${0})`)
|
|
.call(chart);
|
|
}, true);
|
|
},
|
|
};
|
|
}
|
|
|
|
function boxPlotEditor() {
|
|
return {
|
|
restrict: 'E',
|
|
template: editorTemplate,
|
|
};
|
|
}
|
|
|
|
export default function init(ngModule) {
|
|
ngModule.directive('boxplotRenderer', boxPlotRenderer);
|
|
ngModule.directive('boxplotEditor', boxPlotEditor);
|
|
|
|
ngModule.config((VisualizationProvider) => {
|
|
const renderTemplate =
|
|
'<boxplot-renderer ' +
|
|
'options="visualization.options" query-result="queryResult">' +
|
|
'</boxplot-renderer>';
|
|
|
|
const editTemplate = '<boxplot-editor></boxplot-editor>';
|
|
|
|
VisualizationProvider.registerVisualization({
|
|
type: 'BOXPLOT',
|
|
name: 'Boxplot (Deprecated)',
|
|
renderTemplate,
|
|
editorTemplate: editTemplate,
|
|
});
|
|
});
|
|
}
|