mirror of
https://github.com/valitydev/redash.git
synced 2024-11-07 09:28:51 +00:00
getredash/redash#2117 Annotations
This commit is contained in:
parent
0cefed7108
commit
4547b8b735
@ -32,6 +32,9 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-if="['line', 'area', 'column'].indexOf(options.globalSeriesType) >= 0" class="checkbox">
|
||||
<label><input type="checkbox" ng-model="options.annotations"> Show annotations</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{'has-error': chartEditor.xAxisColumn.$invalid}">
|
||||
<label class="control-label">X Column</label>
|
||||
|
@ -35,6 +35,12 @@ const PlotlyChart = () => ({
|
||||
let layout = {};
|
||||
let data = [];
|
||||
|
||||
const applyAutoMargins = debounce(() => {
|
||||
if (applyMargins(layout.margin, calculateMargins(plotlyElement))) {
|
||||
Plotly.relayout(plotlyElement, layout);
|
||||
}
|
||||
}, 100);
|
||||
|
||||
function update() {
|
||||
if (['normal', 'percent'].indexOf(scope.options.series.stacking) >= 0) {
|
||||
// Backward compatibility
|
||||
@ -47,31 +53,26 @@ const PlotlyChart = () => ({
|
||||
data = prepareData(scope.series, scope.options);
|
||||
updateStacking(data, scope.options);
|
||||
layout = prepareLayout(plotlyElement, scope.series, scope.options, data);
|
||||
|
||||
Plotly.purge(plotlyElement);
|
||||
Plotly.newPlot(plotlyElement, data, layout, plotlyOptions);
|
||||
}
|
||||
update();
|
||||
|
||||
const applyAutoMargins = debounce(() => {
|
||||
if (applyMargins(layout.margin, calculateMargins(plotlyElement))) {
|
||||
Plotly.relayout(plotlyElement, layout);
|
||||
}
|
||||
}, 100);
|
||||
plotlyElement.on('plotly_afterplot', () => {
|
||||
applyAutoMargins();
|
||||
|
||||
plotlyElement.on('plotly_afterplot', () => {
|
||||
applyAutoMargins();
|
||||
plotlyElement.querySelectorAll('.legendtoggle').forEach((rectDiv, i) => {
|
||||
d3.select(rectDiv).on('click', () => {
|
||||
const maxIndex = scope.data.length - 1;
|
||||
const itemClicked = scope.data[maxIndex - i];
|
||||
|
||||
plotlyElement.querySelectorAll('.legendtoggle').forEach((rectDiv, i) => {
|
||||
d3.select(rectDiv).on('click', () => {
|
||||
const maxIndex = scope.data.length - 1;
|
||||
const itemClicked = scope.data[maxIndex - i];
|
||||
|
||||
itemClicked.visible = (itemClicked.visible === true) ? 'legendonly' : true;
|
||||
updateStacking(data, scope.options);
|
||||
Plotly.redraw(plotlyElement);
|
||||
itemClicked.visible = (itemClicked.visible === true) ? 'legendonly' : true;
|
||||
updateStacking(data, scope.options);
|
||||
Plotly.redraw(plotlyElement);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
update();
|
||||
|
||||
scope.$watch('series', (oldValue, newValue) => {
|
||||
if (oldValue !== newValue) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {
|
||||
isArray, isNumber, isUndefined, contains, min, max, has, find,
|
||||
isArray, isNumber, isUndefined, contains, min, max, has, find, first, last,
|
||||
each, values, sortBy, union, pluck, identity, filter, map, constant,
|
||||
} from 'underscore';
|
||||
import moment from 'moment';
|
||||
@ -241,13 +241,80 @@ function prepareStackingData(seriesList) {
|
||||
});
|
||||
});
|
||||
|
||||
return seriesList;
|
||||
}
|
||||
|
||||
function enableAnnotations(layout, seriesList, options) {
|
||||
if (options.annotations) {
|
||||
if (options.globalSeriesType === 'column') {
|
||||
seriesList.forEach((series) => {
|
||||
series.textposition = 'auto';
|
||||
});
|
||||
} else if (['line', 'area'].indexOf(options.globalSeriesType) >= 0) {
|
||||
layout.yaxis.showticklabels = false;
|
||||
|
||||
delete layout.yaxis2;
|
||||
layout.annotations = [];
|
||||
each(seriesList, (series) => {
|
||||
delete series.yaxis;
|
||||
const leftAnnotation = {
|
||||
xref: 'paper',
|
||||
x: 0.05,
|
||||
y: first(series.y),
|
||||
xanchor: 'right',
|
||||
yanchor: 'middle',
|
||||
text: first(series.annotations),
|
||||
showarrow: false,
|
||||
};
|
||||
const rightAnnotation = {
|
||||
xref: 'paper',
|
||||
x: 0.95,
|
||||
y: last(series.y),
|
||||
xanchor: 'left',
|
||||
yanchor: 'middle',
|
||||
text: last(series.annotations),
|
||||
showarrow: false,
|
||||
};
|
||||
|
||||
layout.annotations.push(leftAnnotation, rightAnnotation);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function prepareDataLabels(seriesList, options) {
|
||||
seriesList.forEach((series) => {
|
||||
series.visible = true;
|
||||
series.savedY = series.y;
|
||||
series.hoverinfo = 'x+text+name';
|
||||
series.text = map(series.y, v => `Value: ${formatNumber(v)}`);
|
||||
series.annotations = map([first(series.y), last(series.y)], v => `${formatNumber(v)}`);
|
||||
});
|
||||
|
||||
if (options.series.percentValues && (seriesList.length > 0)) {
|
||||
const sumOfCorrespondingPoints = map(seriesList[0].savedY, constant(0));
|
||||
each(seriesList, (series) => {
|
||||
each(series.savedY, (v, i) => {
|
||||
sumOfCorrespondingPoints[i] += Math.abs(v);
|
||||
});
|
||||
});
|
||||
|
||||
each(seriesList, (series) => {
|
||||
series.y = map(series.savedY, (v, i) => Math.sign(v) * Math.abs(v) / sumOfCorrespondingPoints[i] * 100);
|
||||
series.text = map(series.y, (v, i) => [
|
||||
`Value: ${formatNumber(series.savedY[i])}`,
|
||||
'<br>',
|
||||
`Relative: ${formatPercent(v)}%`,
|
||||
].join(''));
|
||||
series.annotations = map([first(series.y), last(series.y)], (v, i) => [
|
||||
`${formatNumber(series.savedY[i])}`,
|
||||
`(${formatPercent(v)}%)`,
|
||||
].join(' '));
|
||||
series.savedY = series.y; // Now we don't need absolute values, only percent values will be used
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return seriesList;
|
||||
}
|
||||
|
||||
@ -257,25 +324,7 @@ export function prepareData(seriesList, options) {
|
||||
}
|
||||
|
||||
const result = prepareStackingData(prepareChartData(seriesList, options));
|
||||
if (options.series.percentValues && (result.length > 0)) {
|
||||
const sumOfCorrespondingPoints = map(result[0].savedY, constant(0));
|
||||
each(result, (series) => {
|
||||
each(series.savedY, (v, i) => {
|
||||
sumOfCorrespondingPoints[i] += Math.abs(v);
|
||||
});
|
||||
});
|
||||
|
||||
each(result, (series) => {
|
||||
series.y = map(series.savedY, (v, i) => Math.sign(v) * Math.abs(v) / sumOfCorrespondingPoints[i] * 100);
|
||||
series.text = map(series.y, (v, i) => [
|
||||
`Value: ${formatNumber(series.savedY[i])}`,
|
||||
'<br>',
|
||||
`Relative: ${formatPercent(v)}%`,
|
||||
].join(''));
|
||||
series.savedY = series.y; // Now we don't need absolute values, only percent values will be used
|
||||
});
|
||||
}
|
||||
return result;
|
||||
return prepareDataLabels(result, options);
|
||||
}
|
||||
|
||||
export function prepareLayout(element, seriesList, options, data) {
|
||||
@ -362,6 +411,8 @@ export function prepareLayout(element, seriesList, options, data) {
|
||||
}
|
||||
}
|
||||
|
||||
enableAnnotations(result, data, options);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -405,6 +456,16 @@ export function calculateMargins(element) {
|
||||
}
|
||||
});
|
||||
|
||||
const annotations = element.querySelectorAll('.annotation-text');
|
||||
if (annotations.length > 0) {
|
||||
const annotationsSize = max(map(annotations, (ann) => {
|
||||
const bounds = ann.getBoundingClientRect();
|
||||
return Math.ceil(bounds.width);
|
||||
}));
|
||||
result.l = Math.max(result.l || 0, annotationsSize);
|
||||
result.r = Math.max(result.r || 0, annotationsSize);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user