Files
redash/client/app/visualizations/chart/plotly/updateData.js
Arik Fraimovich 56d3be2248 Prettier all the Javascript code & GitHub Action (#4433)
* Prettier all the JS files

* Add GitHub Action to autoformat code pushed to master

* Fix eslint violation due to formatting.

* Remove GitHub actions for styling

* Add restyled.io config
2019-12-11 17:05:38 +02:00

230 lines
6.9 KiB
JavaScript

import { isNil, each, extend, filter, identity, includes, map, sortBy } from "lodash";
import { createNumberFormatter, formatSimpleTemplate } from "@/lib/value-format";
import { normalizeValue } from "./utils";
function shouldUseUnifiedXAxis(options) {
return options.sortX && options.xAxis.type === "category" && options.globalSeriesType !== "box";
}
function defaultFormatSeriesText(item) {
let result = item["@@y"];
if (item["@@yError"] !== undefined) {
result = `${result} \u00B1 ${item["@@yError"]}`;
}
if (item["@@yPercent"] !== undefined) {
result = `${item["@@yPercent"]} (${result})`;
}
if (item["@@size"] !== undefined) {
result = `${result}: ${item["@@size"]}`;
}
return result;
}
function defaultFormatSeriesTextForPie(item) {
return item["@@yPercent"] + " (" + item["@@y"] + ")";
}
function createTextFormatter(options) {
if (options.textFormat === "") {
return options.globalSeriesType === "pie" ? defaultFormatSeriesTextForPie : defaultFormatSeriesText;
}
return item => formatSimpleTemplate(options.textFormat, item);
}
function formatValue(value, axis, options) {
let axisType = null;
switch (axis) {
case "x":
axisType = options.xAxis.type;
break;
case "y":
axisType = options.yAxis[0].type;
break;
case "y2":
axisType = options.yAxis[1].type;
break;
// no default
}
return normalizeValue(value, axisType, options.dateTimeFormat);
}
function updateSeriesText(seriesList, options) {
const formatNumber = createNumberFormatter(options.numberFormat);
const formatPercent = createNumberFormatter(options.percentFormat);
const formatText = createTextFormatter(options);
const defaultY = options.missingValuesAsZero ? 0.0 : null;
each(seriesList, series => {
const seriesOptions = options.seriesOptions[series.name] || { type: options.globalSeriesType };
series.text = [];
series.hover = [];
const xValues = options.globalSeriesType === "pie" ? series.labels : series.x;
xValues.forEach(x => {
const text = {
"@@name": series.name,
};
const item = series.sourceData.get(x) || { x, y: defaultY, row: { x, y: defaultY } };
const yValueIsAny = includes(["bubble", "scatter"], seriesOptions.type);
// for `formatValue` we have to use original value of `x` and `y`: `item.x`/`item.y` contains value
// already processed with `normalizeValue`, and if they were `moment` instances - they are formatted
// using default (ISO) date/time format. Here we need to use custom date/time format, so we pass original value
// to `formatValue` which will call `normalizeValue` again, but this time with different date/time format
// (if needed)
text["@@x"] = formatValue(item.row.x, "x", options);
text["@@y"] = yValueIsAny ? formatValue(item.row.y, series.yaxis, options) : formatNumber(item.y);
if (item.yError !== undefined) {
text["@@yError"] = formatNumber(item.yError);
}
if (item.size !== undefined) {
text["@@size"] = formatNumber(item.size);
}
if (options.series.percentValues || options.globalSeriesType === "pie") {
text["@@yPercent"] = formatPercent(Math.abs(item.yPercent));
}
extend(text, item.row.$raw);
series.text.push(formatText(text));
});
});
}
function updatePercentValues(seriesList, options) {
if (options.series.percentValues) {
// Some series may not have corresponding x-values;
// do calculations for each x only for series that do have that x
const sumOfCorrespondingPoints = new Map();
each(seriesList, series => {
series.sourceData.forEach(item => {
const sum = sumOfCorrespondingPoints.get(item.x) || 0;
sumOfCorrespondingPoints.set(item.x, sum + Math.abs(item.y || 0.0));
});
});
each(seriesList, series => {
const yValues = [];
series.sourceData.forEach(item => {
if (isNil(item.y) && !options.missingValuesAsZero) {
item.yPercent = null;
} else {
const sum = sumOfCorrespondingPoints.get(item.x);
item.yPercent = (item.y / sum) * 100;
}
yValues.push(item.yPercent);
});
series.y = yValues;
});
}
}
function getUnifiedXAxisValues(seriesList, sorted) {
const set = new Set();
each(seriesList, series => {
// `Map.forEach` will walk items in insertion order
series.sourceData.forEach(item => {
set.add(item.x);
});
});
const result = [...set];
return sorted ? sortBy(result, identity) : result;
}
function updateUnifiedXAxisValues(seriesList, options) {
const unifiedX = getUnifiedXAxisValues(seriesList, options.sortX);
const defaultY = options.missingValuesAsZero ? 0.0 : null;
each(seriesList, series => {
series.x = [];
series.y = [];
series.error_y.array = [];
each(unifiedX, x => {
series.x.push(x);
const item = series.sourceData.get(x);
if (item) {
series.y.push(options.series.percentValues ? item.yPercent : item.y);
series.error_y.array.push(item.yError);
} else {
series.y.push(defaultY);
series.error_y.array.push(null);
}
});
});
}
function updatePieData(seriesList, options) {
updateSeriesText(seriesList, options);
}
function updateLineAreaData(seriesList, options) {
// Apply "percent values" modification
updatePercentValues(seriesList, options);
if (options.series.stacking) {
updateUnifiedXAxisValues(seriesList, options);
// Calculate cumulative value for each x tick
const cumulativeValues = {};
each(seriesList, series => {
series.y = map(series.y, (y, i) => {
if (isNil(y) && !options.missingValuesAsZero) {
return null;
}
const x = series.x[i];
const stackedY = y + (cumulativeValues[x] || 0.0);
cumulativeValues[x] = stackedY;
return stackedY;
});
});
} else {
if (shouldUseUnifiedXAxis(options)) {
updateUnifiedXAxisValues(seriesList, options);
}
}
// Finally - update text labels
updateSeriesText(seriesList, options);
}
function updateDefaultData(seriesList, options) {
// Apply "percent values" modification
updatePercentValues(seriesList, options);
if (!options.series.stacking) {
if (shouldUseUnifiedXAxis(options)) {
updateUnifiedXAxisValues(seriesList, options);
}
}
// Finally - update text labels
updateSeriesText(seriesList, options);
}
export default function updateData(seriesList, options) {
// Use only visible series
const visibleSeriesList = filter(seriesList, s => s.visible === true);
if (visibleSeriesList.length > 0) {
switch (options.globalSeriesType) {
case "pie":
updatePieData(visibleSeriesList, options);
break;
case "line":
case "area":
updateLineAreaData(visibleSeriesList, options);
break;
case "heatmap":
break;
default:
updateDefaultData(visibleSeriesList, options);
break;
}
}
return seriesList;
}