d2-charts-api
Advanced tools
Comparing version
@@ -6,25 +6,20 @@ 'use strict'; | ||
}); | ||
exports.REGRESSION_TYPE_LINEAR = undefined; | ||
exports.default = function (series, isStacked) { | ||
exports.default = function (regressionType, series, isStacked) { | ||
var newSeries = []; | ||
if (isStacked) { | ||
return [].concat(_toConsumableArray(series), [Object.assign({}, DEFAULT_TRENDLINE, { | ||
data: getRegressionData((0, _getStackedData2.default)(series, true)) | ||
})]); | ||
newSeries.push.apply(newSeries, _toConsumableArray(series).concat([Object.assign({}, getRegressionObj((0, _getStackedData2.default)(series), regressionType))])); | ||
} else { | ||
var newSeries = []; | ||
series.forEach(function (seriesObj) { | ||
newSeries.push(seriesObj, Object.assign({}, DEFAULT_TRENDLINE, { | ||
color: getDarkerColor(seriesObj.color), | ||
data: getRegressionData(seriesObj.data) | ||
newSeries.push(seriesObj, Object.assign({}, getRegressionObj(seriesObj.data, regressionType), { | ||
name: seriesObj.name + ' (trend)', | ||
color: getDarkerColor(seriesObj.color) | ||
})); | ||
}); | ||
} | ||
return newSeries; | ||
} | ||
return newSeries; | ||
}; | ||
var _jqplot_regression = require('../../../util/regression/jqplot_regression'); | ||
var _d3Color = require('d3-color'); | ||
@@ -40,6 +35,4 @@ | ||
var REGRESSION_TYPE_LINEAR = exports.REGRESSION_TYPE_LINEAR = 'LINEAR'; | ||
var DEFAULT_TRENDLINE = { | ||
type: 'line', | ||
type: 'spline', | ||
name: 'Trend', | ||
@@ -50,2 +43,3 @@ dashStyle: 'solid', | ||
marker: { | ||
enabled: false, | ||
symbol: 'circle', | ||
@@ -56,24 +50,476 @@ radius: 2 | ||
function getAdaptedRegressionData(data) { | ||
return data.map(function (array) { | ||
return array[1]; | ||
function getColor(colors, index) { | ||
return colors[index] || getColor(colors, index - colors.length); | ||
} | ||
function getDarkerColor(color) { | ||
return (0, _d3Color.rgb)(color).darker(0.5).toString(); | ||
} | ||
function getRegressionData(data) { | ||
return data.map(function (value, index) { | ||
return [index, value]; | ||
}); | ||
} | ||
function getRegressionData(data, isClean) { | ||
var adaptedRegressionData = getAdaptedRegressionData((0, _jqplot_regression.fitData)(data).data); | ||
var i = 0; | ||
function getRegressionObj(data, regressionType) { | ||
// LINEAR: | ||
// - decimalPlaces (default = 2) | ||
// LOESS: | ||
// - loessSmooth (default = 25) | ||
// POLYNOMIAL: | ||
// - order (default = 2) | ||
// - extrapolate (default = 0) | ||
return isClean ? adaptedRegressionData : data.map(function (value, index) { | ||
return value === null ? value : adaptedRegressionData[i++]; | ||
var regression = void 0; | ||
var regressionTypeOptions = {}; | ||
switch (regressionType) { | ||
case 'LINEAR': | ||
// linear(data, decimalPlaces) | ||
regression = linear(getRegressionData(data)); | ||
regressionTypeOptions.type = 'line'; | ||
break; | ||
case 'POLYNOMIAL': | ||
// polynomial(data, order, extrapolate) | ||
regression = polynomial(getRegressionData(data), 2, 0); | ||
break; | ||
case 'LOESS': | ||
// loess(data, smoothing) | ||
regression = loess(getRegressionData(data), 0.25); | ||
break; | ||
}; | ||
return Object.assign({}, DEFAULT_TRENDLINE, regressionTypeOptions, { | ||
data: regression.points | ||
}); | ||
} | ||
function getColor(colors, index) { | ||
return colors[index] || getColor(colors, index - colors.length); | ||
// Code extracted from https://github.com/Tom-Alexander/regression-js/ | ||
function gaussianElimination(a, o) { | ||
var maxrow = 0, | ||
tmp = 0, | ||
n = a.length - 1, | ||
x = new Array(o); | ||
for (var i = 0; i < n; i++) { | ||
maxrow = i; | ||
for (var j = i + 1; j < n; j++) { | ||
if (Math.abs(a[i][j]) > Math.abs(a[i][maxrow])) { | ||
maxrow = j; | ||
} | ||
} | ||
for (var k = i; k < n + 1; k++) { | ||
tmp = a[k][i]; | ||
a[k][i] = a[k][maxrow]; | ||
a[k][maxrow] = tmp; | ||
} | ||
for (var _j = i + 1; _j < n; _j++) { | ||
for (var _k = n; _k >= i; _k--) { | ||
a[_k][_j] -= a[_k][i] * a[i][_j] / a[i][i]; | ||
} | ||
} | ||
} | ||
for (var _j2 = n - 1; _j2 >= 0; _j2--) { | ||
tmp = 0; | ||
for (var _k2 = _j2 + 1; _k2 < n; _k2++) { | ||
tmp += a[_k2][_j2] * x[_k2]; | ||
} | ||
x[_j2] = (a[n][_j2] - tmp) / a[_j2][_j2]; | ||
} | ||
return x; | ||
} | ||
function getDarkerColor(color) { | ||
return (0, _d3Color.rgb)(color).darker(0.5).toString(); | ||
// Code extracted from https://github.com/Tom-Alexander/regression-js/ | ||
// Human readable formulas: | ||
// | ||
// N * Σ(XY) - Σ(X) | ||
// intercept = --------------------- | ||
// N * Σ(X^2) - Σ(X)^2 | ||
// | ||
// correlation = N * Σ(XY) - Σ(X) * Σ (Y) / √ ( N * Σ(X^2) - Σ(X) ) * ( N * Σ(Y^2) - Σ(Y)^2 ) ) ) | ||
function linear(data, decimalPlaces) { | ||
var sum = [0, 0, 0, 0, 0], | ||
results = [], | ||
N = data.length; | ||
for (var _n = 0, len = data.length; _n < len; _n++) { | ||
if (data[_n]['x'] != null) { | ||
data[_n][0] = data[_n].x; | ||
data[_n][1] = data[_n].y; | ||
} | ||
if (data[_n][1] != null) { | ||
sum[0] += data[_n][0]; // Σ(X) | ||
sum[1] += data[_n][1]; // Σ(Y) | ||
sum[2] += data[_n][0] * data[_n][0]; // Σ(X^2) | ||
sum[3] += data[_n][0] * data[_n][1]; // Σ(XY) | ||
sum[4] += data[_n][1] * data[_n][1]; // Σ(Y^2) | ||
} else { | ||
N -= 1; | ||
} | ||
} | ||
var gradient = (N * sum[3] - sum[0] * sum[1]) / (N * sum[2] - sum[0] * sum[0]); | ||
var intercept = sum[1] / N - gradient * sum[0] / N; | ||
// let correlation = (N * sum[3] - sum[0] * sum[1]) / Math.sqrt((N * sum[2] - sum[0] * sum[0]) * (N * sum[4] - sum[1] * sum[1])); | ||
for (var i = 0, _len = data.length; i < _len; i++) { | ||
var coorY = data[i][0] * gradient + intercept; | ||
if (decimalPlaces) { | ||
coorY = parseFloat(coorY.toFixed(decimalPlaces)); | ||
} | ||
var coordinate = [data[i][0], coorY]; | ||
results.push(coordinate); | ||
} | ||
results.sort(function (a, b) { | ||
if (a[0] > b[0]) { | ||
return 1; | ||
} | ||
if (a[0] < b[0]) { | ||
return -1; | ||
} | ||
return 0; | ||
}); | ||
var string = 'y = ' + Math.round(gradient * 100) / 100 + 'x + ' + Math.round(intercept * 100) / 100; | ||
return { | ||
equation: [gradient, intercept], | ||
points: results, | ||
string: string | ||
}; | ||
} | ||
// Code extracted from https://github.com/Tom-Alexander/regression-js/ | ||
function logarithmic(data) { | ||
var sum = [0, 0, 0, 0], | ||
results = [], | ||
mean = 0; | ||
for (var _n2 = 0, len = data.length; _n2 < len; _n2++) { | ||
if (data[_n2].x != null) { | ||
data[_n2][0] = data[_n2].x; | ||
data[_n2][1] = data[_n2].y; | ||
} | ||
if (data[_n2][1] != null) { | ||
sum[0] += Math.log(data[_n2][0]); | ||
sum[1] += data[_n2][1] * Math.log(data[_n2][0]); | ||
sum[2] += data[_n2][1]; | ||
sum[3] += Math.pow(Math.log(data[_n2][0]), 2); | ||
} | ||
} | ||
var B = (n * sum[1] - sum[2] * sum[0]) / (n * sum[3] - sum[0] * sum[0]); | ||
var A = (sum[2] - B * sum[0]) / n; | ||
for (var i = 0, _len2 = data.length; i < _len2; i++) { | ||
var coordinate = [data[i][0], A + B * Math.log(data[i][0])]; | ||
results.push(coordinate); | ||
} | ||
results.sort(function (a, b) { | ||
if (a[0] > b[0]) { | ||
return 1; | ||
} | ||
if (a[0] < b[0]) { | ||
return -1; | ||
} | ||
return 0; | ||
}); | ||
var string = 'y = ' + Math.round(A * 100) / 100 + ' + ' + Math.round(B * 100) / 100 + ' ln(x)'; | ||
return { | ||
equation: [A, B], | ||
points: results, | ||
string: string | ||
}; | ||
} | ||
// Code extracted from https://github.com/Tom-Alexander/regression-js/ | ||
function power(data) { | ||
var sum = [0, 0, 0, 0], | ||
results = []; | ||
for (var _n3 = 0, len = data.length; _n3 < len; _n3++) { | ||
if (data[_n3].x != null) { | ||
data[_n3][0] = data[_n3].x; | ||
data[_n3][1] = data[_n3].y; | ||
} | ||
if (data[_n3][1] != null) { | ||
sum[0] += Math.log(data[_n3][0]); | ||
sum[1] += Math.log(data[_n3][1]) * Math.log(data[_n3][0]); | ||
sum[2] += Math.log(data[_n3][1]); | ||
sum[3] += Math.pow(Math.log(data[_n3][0]), 2); | ||
} | ||
} | ||
var B = (n * sum[1] - sum[2] * sum[0]) / (n * sum[3] - sum[0] * sum[0]); | ||
var A = Math.pow(Math.E, (sum[2] - B * sum[0]) / n); | ||
for (var i = 0, _len3 = data.length; i < _len3; i++) { | ||
var coordinate = [data[i][0], A * Math.pow(data[i][0], B)]; | ||
results.push(coordinate); | ||
} | ||
results.sort(function (a, b) { | ||
if (a[0] > b[0]) { | ||
return 1; | ||
} | ||
if (a[0] < b[0]) { | ||
return -1; | ||
} | ||
return 0; | ||
}); | ||
var string = 'y = ' + Math.round(A * 100) / 100 + 'x^' + Math.round(B * 100) / 100; | ||
return { | ||
equation: [A, B], | ||
points: results, | ||
string: string | ||
}; | ||
} | ||
// Code extracted from https://github.com/Tom-Alexander/regression-js/ | ||
function polynomial(data, order, extrapolate) { | ||
if (typeof order == 'undefined') { | ||
order = 2; | ||
} | ||
var lhs = [], | ||
rhs = [], | ||
results = [], | ||
a = 0, | ||
b = 0, | ||
k = order + 1; | ||
for (var i = 0; i < k; i++) { | ||
for (var l = 0, len = data.length; l < len; l++) { | ||
if (data[l].x != null) { | ||
data[l][0] = data[l].x; | ||
data[l][1] = data[l].y; | ||
} | ||
if (data[l][1] != null) { | ||
a += Math.pow(data[l][0], i) * data[l][1]; | ||
} | ||
} | ||
lhs.push(a); | ||
a = 0; | ||
var c = []; | ||
for (var j = 0; j < k; j++) { | ||
for (var _l = 0, _len4 = data.length; _l < _len4; _l++) { | ||
if (data[_l][1]) { | ||
b += Math.pow(data[_l][0], i + j); | ||
} | ||
} | ||
c.push(b); | ||
b = 0; | ||
} | ||
rhs.push(c); | ||
} | ||
rhs.push(lhs); | ||
var equation = gaussianElimination(rhs, k); | ||
var resultLength = data.length + extrapolate; | ||
var step = data[data.length - 1][0] - data[data.length - 2][0]; | ||
for (var _i = 0, _len5 = resultLength; _i < _len5; _i++) { | ||
var answer = 0, | ||
x = 0; | ||
if (typeof data[_i] !== 'undefined') { | ||
x = data[_i][0]; | ||
} else { | ||
x = data[data.length - 1][0] + (_i - data.length) * step; | ||
} | ||
for (var w = 0; w < equation.length; w++) { | ||
answer += equation[w] * Math.pow(x, w); | ||
} | ||
results.push([x, answer]); | ||
} | ||
results.sort(function (a, b) { | ||
if (a[0] > b[0]) { | ||
return 1; | ||
} | ||
if (a[0] < b[0]) { | ||
return -1; | ||
} | ||
return 0; | ||
}); | ||
var string = 'y = '; | ||
for (var _i2 = equation.length - 1; _i2 >= 0; _i2--) { | ||
if (_i2 > 1) { | ||
string += Math.round(equation[_i2] * 100) / 100 + 'x^' + _i2 + ' + '; | ||
} else if (_i2 == 1) { | ||
string += Math.round(equation[_i2] * 100) / 100 + 'x' + ' + '; | ||
} else { | ||
string += Math.round(equation[_i2] * 100) / 100; | ||
} | ||
} | ||
return { | ||
equation: equation, | ||
points: results, | ||
string: string | ||
}; | ||
} | ||
// @author: Ignacio Vazquez | ||
// Based on | ||
// - http://commons.apache.org/proper/commons-math/download_math.cgi LoesInterpolator.java | ||
// - https://gist.github.com/avibryant/1151823 | ||
function loess(data, bandwidth) { | ||
bandwidth = bandwidth || 0.25; | ||
var xval = data.map(function (pair) { | ||
return pair[0]; | ||
}); | ||
var distinctX = array_unique(xval); | ||
if (2 / distinctX.length > bandwidth) { | ||
bandwidth = Math.min(2 / distinctX.length, 1); | ||
console.warn("updated bandwith to " + bandwidth); | ||
} | ||
var yval = data.map(function (pair) { | ||
return pair[1]; | ||
}); | ||
function array_unique(values) { | ||
var o = {}, | ||
i = void 0, | ||
l = values.length, | ||
r = []; | ||
for (i = 0; i < l; i += 1) { | ||
o[values[i]] = values[i]; | ||
} | ||
for (i in o) { | ||
r.push(o[i]); | ||
} | ||
return r; | ||
} | ||
function tricube(x) { | ||
var tmp = 1 - x * x * x; | ||
return tmp * tmp * tmp; | ||
} | ||
var res = []; | ||
var left = 0; | ||
var right = Math.floor(bandwidth * xval.length) - 1; | ||
for (var i in xval) { | ||
var x = xval[i]; | ||
if (i > 0) { | ||
if (right < xval.length - 1 && xval[right + 1] - xval[i] < xval[i] - xval[left]) { | ||
left++; | ||
right++; | ||
} | ||
} | ||
//console.debug("left: "+left + " right: " + right ); | ||
var edge = void 0; | ||
if (xval[i] - xval[left] > xval[right] - xval[i]) { | ||
edge = left; | ||
} else { | ||
edge = right; | ||
} | ||
var denom = Math.abs(1.0 / (xval[edge] - x)); | ||
var sumWeights = 0; | ||
var sumX = 0, | ||
sumXSquared = 0, | ||
sumY = 0, | ||
sumXY = 0; | ||
var k = left; | ||
while (k <= right) { | ||
var xk = xval[k]; | ||
var yk = yval[k]; | ||
var dist = void 0; | ||
if (k < i) { | ||
dist = x - xk; | ||
} else { | ||
dist = xk - x; | ||
} | ||
var w = tricube(dist * denom); | ||
var xkw = xk * w; | ||
sumWeights += w; | ||
sumX += xkw; | ||
sumXSquared += xk * xkw; | ||
sumY += yk * w; | ||
sumXY += yk * xkw; | ||
k++; | ||
} | ||
var meanX = sumX / sumWeights; | ||
//console.debug(meanX); | ||
var meanY = sumY / sumWeights; | ||
var meanXY = sumXY / sumWeights; | ||
var meanXSquared = sumXSquared / sumWeights; | ||
var beta = void 0; | ||
if (meanXSquared == meanX * meanX) { | ||
beta = 0; | ||
} else { | ||
beta = (meanXY - meanX * meanY) / (meanXSquared - meanX * meanX); | ||
} | ||
var alpha = meanY - beta * meanX; | ||
res[i] = beta * x + alpha; | ||
} | ||
//console.debug(res); | ||
return { | ||
equation: "", | ||
points: xval.map(function (x, i) { | ||
return [x, res[i]]; | ||
}), | ||
string: "" | ||
}; | ||
} | ||
//# sourceMappingURL=addTrendLines.js.map |
@@ -74,4 +74,5 @@ 'use strict'; | ||
// DHIS2-1243 add trend lines after sorting | ||
if (layout.regressionType === _addTrendLines.REGRESSION_TYPE_LINEAR) { | ||
config.series = (0, _addTrendLines2.default)(config.series, isStacked); | ||
// trend line on pie and gauge does not make sense | ||
if (layout.type !== CHART_TYPE_GAUGE && layout.type !== CHART_TYPE_PIE && layout.regressionType !== 'NONE') { | ||
config.series = (0, _addTrendLines2.default)(layout.regressionType, config.series, isStacked); | ||
} | ||
@@ -78,0 +79,0 @@ |
{ | ||
"name": "d2-charts-api", | ||
"version": "27.0.9", | ||
"version": "27.0.10", | ||
"description": "DHIS2 charts api", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -1,9 +0,6 @@ | ||
import { fitData } from '../../../util/regression/jqplot_regression'; | ||
import { rgb } from 'd3-color'; | ||
import getStackedData from './getStackedData'; | ||
export const REGRESSION_TYPE_LINEAR = 'LINEAR'; | ||
const DEFAULT_TRENDLINE = { | ||
type: 'line', | ||
type: 'spline', | ||
name: 'Trend', | ||
@@ -14,2 +11,3 @@ dashStyle: 'solid', | ||
marker: { | ||
enabled: false, | ||
symbol: 'circle', | ||
@@ -20,11 +18,31 @@ radius: 2 | ||
function getAdaptedRegressionData(data) { | ||
return data.map(array => array[1]); | ||
} | ||
export default function (regressionType, series, isStacked) { | ||
const newSeries = []; | ||
function getRegressionData(data, isClean) { | ||
const adaptedRegressionData = getAdaptedRegressionData(fitData(data).data); | ||
let i = 0; | ||
if (isStacked) { | ||
newSeries.push( | ||
...series, | ||
Object.assign( | ||
{}, | ||
getRegressionObj(getStackedData(series), regressionType), | ||
) | ||
); | ||
} | ||
else { | ||
series.forEach(seriesObj => { | ||
newSeries.push( | ||
seriesObj, | ||
Object.assign( | ||
{}, | ||
getRegressionObj(seriesObj.data, regressionType), | ||
{ | ||
name: seriesObj.name + ' (trend)', | ||
color: getDarkerColor(seriesObj.color), | ||
} | ||
) | ||
); | ||
}); | ||
} | ||
return isClean ? adaptedRegressionData : data.map((value, index) => value === null ? value : adaptedRegressionData[i++]); | ||
return newSeries; | ||
} | ||
@@ -40,23 +58,435 @@ | ||
export default function (series, isStacked) { | ||
if (isStacked) { | ||
return [ | ||
...series, | ||
Object.assign({}, DEFAULT_TRENDLINE, { | ||
data: getRegressionData(getStackedData(series, true)) | ||
}) | ||
]; | ||
function getRegressionData(data) { | ||
return data.map((value, index) => { | ||
return [index, value]; | ||
}); | ||
} | ||
function getRegressionObj(data, regressionType) { | ||
// LINEAR: | ||
// - decimalPlaces (default = 2) | ||
// LOESS: | ||
// - loessSmooth (default = 25) | ||
// POLYNOMIAL: | ||
// - order (default = 2) | ||
// - extrapolate (default = 0) | ||
let regression; | ||
let regressionTypeOptions = {}; | ||
switch (regressionType) { | ||
case 'LINEAR': | ||
// linear(data, decimalPlaces) | ||
regression = linear(getRegressionData(data)); | ||
regressionTypeOptions.type = 'line'; | ||
break; | ||
case 'POLYNOMIAL': | ||
// polynomial(data, order, extrapolate) | ||
regression = polynomial(getRegressionData(data), 2, 0); | ||
break; | ||
case 'LOESS': | ||
// loess(data, smoothing) | ||
regression = loess(getRegressionData(data), 0.25); | ||
break; | ||
}; | ||
return Object.assign( | ||
{}, | ||
DEFAULT_TRENDLINE, | ||
regressionTypeOptions, | ||
{ | ||
data: regression.points | ||
} | ||
); | ||
} | ||
// Code extracted from https://github.com/Tom-Alexander/regression-js/ | ||
function gaussianElimination(a, o) { | ||
let maxrow = 0, tmp = 0, n = a.length - 1, x = new Array(o); | ||
for (let i = 0; i < n; i++) { | ||
maxrow = i; | ||
for (let j = i + 1; j < n; j++) { | ||
if (Math.abs(a[i][j]) > Math.abs(a[i][maxrow])) { | ||
maxrow = j; | ||
} | ||
} | ||
for (let k = i; k < n + 1; k++) { | ||
tmp = a[k][i]; | ||
a[k][i] = a[k][maxrow]; | ||
a[k][maxrow] = tmp; | ||
} | ||
for (let j = i + 1; j < n; j++) { | ||
for (let k = n; k >= i; k--) { | ||
a[k][j] -= a[k][i] * a[i][j] / a[i][i]; | ||
} | ||
} | ||
} | ||
else { | ||
const newSeries = []; | ||
series.forEach(seriesObj => { | ||
newSeries.push(seriesObj, Object.assign({}, DEFAULT_TRENDLINE, { | ||
color: getDarkerColor(seriesObj.color), | ||
data: getRegressionData(seriesObj.data) | ||
})); | ||
}); | ||
for (let j = n - 1; j >= 0; j--) { | ||
tmp = 0; | ||
return newSeries; | ||
for (let k = j + 1; k < n; k++) { | ||
tmp += a[k][j] * x[k]; | ||
} | ||
x[j] = (a[n][j] - tmp) / a[j][j]; | ||
} | ||
return (x); | ||
} | ||
// Code extracted from https://github.com/Tom-Alexander/regression-js/ | ||
// Human readable formulas: | ||
// | ||
// N * Σ(XY) - Σ(X) | ||
// intercept = --------------------- | ||
// N * Σ(X^2) - Σ(X)^2 | ||
// | ||
// correlation = N * Σ(XY) - Σ(X) * Σ (Y) / √ ( N * Σ(X^2) - Σ(X) ) * ( N * Σ(Y^2) - Σ(Y)^2 ) ) ) | ||
function linear(data, decimalPlaces) { | ||
let sum = [0, 0, 0, 0, 0], results = [], N = data.length; | ||
for (let n = 0, len = data.length; n < len; n++) { | ||
if (data[n]['x'] != null) { | ||
data[n][0] = data[n].x; | ||
data[n][1] = data[n].y; | ||
} | ||
if (data[n][1] != null) { | ||
sum[0] += data[n][0]; // Σ(X) | ||
sum[1] += data[n][1]; // Σ(Y) | ||
sum[2] += data[n][0] * data[n][0]; // Σ(X^2) | ||
sum[3] += data[n][0] * data[n][1]; // Σ(XY) | ||
sum[4] += data[n][1] * data[n][1]; // Σ(Y^2) | ||
} else { | ||
N -= 1; | ||
} | ||
} | ||
let gradient = (N * sum[3] - sum[0] * sum[1]) / (N * sum[2] - sum[0] * sum[0]); | ||
let intercept = (sum[1] / N) - (gradient * sum[0]) / N; | ||
// let correlation = (N * sum[3] - sum[0] * sum[1]) / Math.sqrt((N * sum[2] - sum[0] * sum[0]) * (N * sum[4] - sum[1] * sum[1])); | ||
for (let i = 0, len = data.length; i < len; i++) { | ||
let coorY = data[i][0] * gradient + intercept; | ||
if (decimalPlaces) { | ||
coorY = parseFloat(coorY.toFixed(decimalPlaces)); | ||
} | ||
let coordinate = [data[i][0], coorY]; | ||
results.push(coordinate); | ||
} | ||
results.sort((a, b) => { | ||
if (a[0] > b[0]) { return 1; } | ||
if (a[0] < b[0]) { return -1; } | ||
return 0; | ||
}); | ||
let string = 'y = ' + Math.round(gradient * 100) / 100 + 'x + ' + Math.round(intercept * 100) / 100; | ||
return { | ||
equation: [gradient, intercept], | ||
points: results, | ||
string: string | ||
}; | ||
} | ||
// Code extracted from https://github.com/Tom-Alexander/regression-js/ | ||
function logarithmic(data) { | ||
let sum = [0, 0, 0, 0], results = [], mean = 0; | ||
for (let n = 0, len = data.length; n < len; n++) { | ||
if (data[n].x != null) { | ||
data[n][0] = data[n].x; | ||
data[n][1] = data[n].y; | ||
} | ||
if (data[n][1] != null) { | ||
sum[0] += Math.log(data[n][0]); | ||
sum[1] += data[n][1] * Math.log(data[n][0]); | ||
sum[2] += data[n][1]; | ||
sum[3] += Math.pow(Math.log(data[n][0]), 2); | ||
} | ||
} | ||
let B = (n * sum[1] - sum[2] * sum[0]) / (n * sum[3] - sum[0] * sum[0]); | ||
let A = (sum[2] - B * sum[0]) / n; | ||
for (let i = 0, len = data.length; i < len; i++) { | ||
let coordinate = [data[i][0], A + B * Math.log(data[i][0])]; | ||
results.push(coordinate); | ||
} | ||
results.sort((a, b) => { | ||
if (a[0] > b[0]) { return 1; } | ||
if (a[0] < b[0]) { return -1; } | ||
return 0; | ||
}); | ||
let string = 'y = ' + Math.round(A * 100) / 100 + ' + ' + Math.round(B * 100) / 100 + ' ln(x)'; | ||
return { | ||
equation: [A, B], | ||
points: results, | ||
string: string | ||
}; | ||
} | ||
// Code extracted from https://github.com/Tom-Alexander/regression-js/ | ||
function power(data) { | ||
let sum = [0, 0, 0, 0], results = []; | ||
for (let n = 0, len = data.length; n < len; n++) { | ||
if (data[n].x != null) { | ||
data[n][0] = data[n].x; | ||
data[n][1] = data[n].y; | ||
} | ||
if (data[n][1] != null) { | ||
sum[0] += Math.log(data[n][0]); | ||
sum[1] += Math.log(data[n][1]) * Math.log(data[n][0]); | ||
sum[2] += Math.log(data[n][1]); | ||
sum[3] += Math.pow(Math.log(data[n][0]), 2); | ||
} | ||
} | ||
let B = (n * sum[1] - sum[2] * sum[0]) / (n * sum[3] - sum[0] * sum[0]); | ||
let A = Math.pow(Math.E, (sum[2] - B * sum[0]) / n); | ||
for (let i = 0, len = data.length; i < len; i++) { | ||
let coordinate = [data[i][0], A * Math.pow(data[i][0] , B)]; | ||
results.push(coordinate); | ||
} | ||
results.sort((a,b) => { | ||
if (a[0] > b[0]) { return 1; } | ||
if (a[0] < b[0]) { return -1; } | ||
return 0; | ||
}); | ||
let string = 'y = ' + Math.round(A * 100) / 100 + 'x^' + Math.round(B * 100) / 100; | ||
return { | ||
equation: [A, B], | ||
points: results, | ||
string: string | ||
}; | ||
} | ||
// Code extracted from https://github.com/Tom-Alexander/regression-js/ | ||
function polynomial(data, order, extrapolate) { | ||
if (typeof order == 'undefined') { | ||
order = 2; | ||
} | ||
let lhs = [], rhs = [], results = [], a = 0, b = 0, k = order + 1; | ||
for (let i = 0; i < k; i++) { | ||
for (let l = 0, len = data.length; l < len; l++) { | ||
if (data[l].x != null) { | ||
data[l][0] = data[l].x; | ||
data[l][1] = data[l].y; | ||
} | ||
if (data[l][1] != null) { | ||
a += Math.pow(data[l][0], i) * data[l][1]; | ||
} | ||
} | ||
lhs.push(a); | ||
a = 0; | ||
let c = []; | ||
for (let j = 0; j < k; j++) { | ||
for (let l = 0, len = data.length; l < len; l++) { | ||
if (data[l][1]) { | ||
b += Math.pow(data[l][0], i + j); | ||
} | ||
} | ||
c.push(b); | ||
b = 0; | ||
} | ||
rhs.push(c); | ||
} | ||
rhs.push(lhs); | ||
let equation = gaussianElimination(rhs, k); | ||
let resultLength = data.length + extrapolate; | ||
let step = data[data.length - 1][0] - data[data.length - 2][0]; | ||
for (let i = 0, len = resultLength; i < len; i++) { | ||
let answer = 0, x = 0; | ||
if (typeof data[i] !== 'undefined') { | ||
x = data[i][0]; | ||
} else { | ||
x = data[data.length - 1][0] + (i - data.length) * step; | ||
} | ||
for (let w = 0; w < equation.length; w++) { | ||
answer += equation[w] * Math.pow(x, w); | ||
} | ||
results.push([x, answer]); | ||
} | ||
results.sort((a,b) => { | ||
if (a[0] > b[0]) { return 1; } | ||
if (a[0] < b[0]) { return -1; } | ||
return 0; | ||
}); | ||
let string = 'y = '; | ||
for (let i = equation.length-1; i >= 0; i--) { | ||
if (i > 1) { | ||
string += Math.round(equation[i] * 100) / 100 + 'x^' + i + ' + '; | ||
} | ||
else if (i == 1) { | ||
string += Math.round(equation[i] * 100) / 100 + 'x' + ' + '; | ||
} | ||
else { | ||
string += Math.round(equation[i] * 100) / 100; | ||
} | ||
} | ||
return { | ||
equation: equation, | ||
points: results, | ||
string: string | ||
}; | ||
} | ||
// @author: Ignacio Vazquez | ||
// Based on | ||
// - http://commons.apache.org/proper/commons-math/download_math.cgi LoesInterpolator.java | ||
// - https://gist.github.com/avibryant/1151823 | ||
function loess(data, bandwidth) { | ||
bandwidth = bandwidth || 0.25 ; | ||
let xval = data.map(pair => { return pair[0]; }); | ||
let distinctX = array_unique(xval); | ||
if (2 / distinctX.length > bandwidth) { | ||
bandwidth = Math.min(2 / distinctX.length, 1); | ||
console.warn("updated bandwith to " + bandwidth); | ||
} | ||
let yval = data.map(pair => { return pair[1]; }); | ||
function array_unique(values) { | ||
let o = {}, i, l = values.length, r = []; | ||
for (i = 0; i < l; i += 1) { | ||
o[values[i]] = values[i]; | ||
} | ||
for (i in o) { | ||
r.push(o[i]); | ||
} | ||
return r; | ||
} | ||
function tricube(x) { | ||
let tmp = 1 - x * x * x; | ||
return tmp * tmp * tmp; | ||
} | ||
let res = []; | ||
let left = 0; | ||
let right = Math.floor(bandwidth * xval.length) - 1; | ||
for (let i in xval) { | ||
let x = xval[i]; | ||
if (i > 0) { | ||
if (right < xval.length - 1 && | ||
xval[right+1] - xval[i] < xval[i] - xval[left]) { | ||
left++; | ||
right++; | ||
} | ||
} | ||
//console.debug("left: "+left + " right: " + right ); | ||
let edge; | ||
if (xval[i] - xval[left] > xval[right] - xval[i]) { | ||
edge = left; | ||
} | ||
else { | ||
edge = right; | ||
} | ||
let denom = Math.abs(1.0 / (xval[edge] - x)); | ||
let sumWeights = 0; | ||
let sumX = 0, sumXSquared = 0, sumY = 0, sumXY = 0; | ||
let k = left; | ||
while (k <= right) { | ||
let xk = xval[k]; | ||
let yk = yval[k]; | ||
let dist; | ||
if (k < i) { | ||
dist = (x - xk); | ||
} else { | ||
dist = (xk - x); | ||
} | ||
let w = tricube(dist * denom); | ||
let xkw = xk * w; | ||
sumWeights += w; | ||
sumX += xkw; | ||
sumXSquared += xk * xkw; | ||
sumY += yk * w; | ||
sumXY += yk * xkw; | ||
k++; | ||
} | ||
let meanX = sumX / sumWeights; | ||
//console.debug(meanX); | ||
let meanY = sumY / sumWeights; | ||
let meanXY = sumXY / sumWeights; | ||
let meanXSquared = sumXSquared / sumWeights; | ||
let beta; | ||
if (meanXSquared == meanX * meanX) { | ||
beta = 0; | ||
} | ||
else { | ||
beta = (meanXY - meanX * meanY) / (meanXSquared - meanX * meanX); | ||
} | ||
let alpha = meanY - beta * meanX; | ||
res[i] = beta * x + alpha; | ||
} | ||
//console.debug(res); | ||
return { | ||
equation: "" , | ||
points: xval.map((x,i) => { return [x, res[i]]; }), | ||
string: "" | ||
}; | ||
} |
@@ -14,3 +14,3 @@ import objectClean from 'd2-utilizr/lib/objectClean'; | ||
import getTrimmedConfig from './getTrimmedConfig'; | ||
import addTrendLines, { REGRESSION_TYPE_LINEAR } from './addTrendLines'; | ||
import addTrendLines from './addTrendLines'; | ||
@@ -80,4 +80,5 @@ export const CHART_TYPE_PIE = 'pie'; | ||
// DHIS2-1243 add trend lines after sorting | ||
if (layout.regressionType === REGRESSION_TYPE_LINEAR) { | ||
config.series = addTrendLines(config.series, isStacked); | ||
// trend line on pie and gauge does not make sense | ||
if (layout.type !== CHART_TYPE_GAUGE && layout.type !== CHART_TYPE_PIE && layout.regressionType !== 'NONE') { | ||
config.series = addTrendLines(layout.regressionType, config.series, isStacked); | ||
} | ||
@@ -84,0 +85,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
208065
18.22%3045
17.39%119
-2.46%