getProcessedDataFeaturesSingleSeriesLineChart = (itabular, possiblePeriods, periodType) => {
const itabular_remap = (itabular) => {
const itabularLocal = itabular;
if(itabularLocal.Settings.ColumnDimensionsIds.length > 0) {
const seriesElementsRemapped = itabularLocal.Dimensions[0].Elements.map((d,i) => ({Id: "metric", DataType: "Number"}))
const dateElementsRemapped = itabularLocal.Dimensions[1].Elements.map((d,i) => ({Id: `row${i}`, Name: d.Name, NetType: "Unique"}))
itabularLocal.Dimensions[0] = {
Id: "cells",
Name: "Statistics",
Tiers: [],
Elements: seriesElementsRemapped,
IsStatistic: false,
Count: seriesElementsRemapped.length
}
itabularLocal.Dimensions[1] = {
Id: "month",
Name: "month",
Tiers: [],
Elements: dateElementsRemapped,
IsStatistic: false,
Count: dateElementsRemapped.length
}
itabularLocal.DataPoints = [itabularLocal.DataPoints[0].map((_, colIndex) => itabularLocal.DataPoints.map(row => row[colIndex]))];
}
return itabularLocal
}
const getStartDateOfISOWeek = (w, y) => {
var simple = new Date (Date.UTC(y, 0, 1 + (w - 1) * 7));
var dow = simple.getUTCDay();
var ISOweekStart = simple;
if (dow <= 4)
ISOweekStart.setUTCDate(simple.getUTCDate() - simple.getUTCDay() + 1);
else
ISOweekStart.setUTCDate(simple.getUTCDate() + 8 - simple.getUTCDay());
return ISOweekStart;
}
const getStartDateOfQuarter = (q, y) => {
const quarterStartMonth = (q) => Number(q)*3 - 3;
return new Date (Date.UTC(Number(y), Number(quarterStartMonth(q)), 1))
}
const lastday = (y,m) => new Date (Date.UTC(y, m + 1, 0)).getUTCDate();
const getEndDateOfISOWeek = (w, y) => {
var simple = new Date(Date.UTC(y, 0, 1 + (w - 1) * 7));
var dow = simple.getUTCDay();
var ISOweekEnd = simple;
ISOweekEnd.setUTCDate(simple.getUTCDate() - (simple.getUTCDay() - 1) + 6);
return ISOweekEnd;
}
/*const getPeriodStartFromDate = {
'day': (date) => {
return date
},
'week': (date) => {
const yearWeek = d3.utcFormat("%Y-%W")(date);
return getStartDateOfISOWeek(Number(yearWeek.slice(-2)), Number(yearWeek.slice(0,4)))
},
'month': (date) => {
const yearMonth = d3.utcFormat("%Y-%m")(date);
return new Date (Date.UTC(Number(yearMonth.slice(0,4)), Number(yearMonth.slice(-2)) - 1, 1))
},
'quarter': (date) => {
const yearQuarter = d3.utcFormat("%Y-Q%q")(date);
return getStartDateOfQuarter(Number(yearQuarter.slice(-1)), Number(yearQuarter.slice(0,4)));
},
'year': (date) => {
const year = d3.utcFormat("%Y")(date);
return new Date (Date.UTC(Number(year), 0, 1))
}
};
const getPeriodEndFromDate = {
'day': (date) => {
return date
},
'week': (date) => {
const yearWeek = d3.utcFormat("%Y-%W")(date);
return getEndDateOfISOWeek(Number(yearWeek.slice(-2)), Number(yearWeek.slice(0,4)))
},
'month': (date) => {
const yearMonth = d3.utcFormat("%Y-%m")(date);
const year = Number(yearMonth.slice(0,4));
const month = Number(yearMonth.slice(-2)) - 1;
return new Date (Date.UTC(year, month, lastday(year, month)))
},
'quarter': (date) => {
const yearQuarter = d3.utcFormat("%Y-Q%q")(date);
const quarterEndMonth = (q) => Number(q)*3 - 1;
const month = Number(quarterEndMonth(yearQuarter.slice(-1)));
const year = Number(yearQuarter.slice(0,4));
return new Date (Date.UTC(year, month, lastday(year, month)));
},
'year': (date) => {
const year = d3.utcFormat("%Y")(date);
return new Date (Date.UTC(Number(year), 11, 31))
}
};
const formatY = (yDomain, d) => {
const numberPos = Math.abs(d);
const absMax = d3.max(yDomain.map(x => Math.abs(x)));
const range = d3.max(yDomain) - d3.min(yDomain);
const lessThan = (x, compare) => x <= compare;
//const tickInterval = arr[arr.length-1] - arr[arr.length-2];
const dynamicDecimalPlaceFormat = (numberPos? numberPos < 1? 0: Math.floor(Math.log10(numberPos)): 0);
var digits = 0;
var numberFormat;
//var formatZero;
var formatZero = `.0f`;
if (absMax == 0) {
numberFormat = `.${digits}f`;
//formatZero = d3.formatPrefix(numberFormat, absMax)
}
else if (lessThan(absMax, 1)) {
numberFormat = `.2r`;
//formatZero = d3.formatPrefix(numberFormat, absMax)
}
else if (lessThan(absMax, 10 )) {
digits = 2
numberFormat = `.${digits}r`;
//formatZero = d3.formatPrefix(numberFormat, absMax)
}
else if (lessThan(absMax, 100)) {
digits = Number.isInteger(d)? 0: 1; //for the y-axis, if it's all integers, no decimal. for values if value is integer, no decimal.
numberFormat = `.${digits}f`;
//formatZero = d3.formatPrefix(numberFormat, absMax)
}
else if (lessThan(absMax, Math.pow(10,4))) {
digits = dynamicDecimalPlaceFormat +1;
numberFormat = `,.${isFinite(digits)?digits:3}r`;
//formatZero = d3.formatPrefix(`.0f`, Math.pow(10,2))
//console.log(d,numberFormat)
} else {
if (lessThan(absMax, Math.pow(10,5))) {
digits = (numberPos? d3.max([Math.ceil(Number.isInteger(Math.log10(numberPos/Math.pow(10,2)))? Math.log10(numberPos/Math.pow(10,2)) + 1:Math.log10(numberPos/Math.pow(10,2))), 0]):0);
}
else if (lessThan(absMax, Math.pow(10,6))) {
digits = (numberPos? d3.max([Math.floor(Math.log10(numberPos/Math.pow(10,2))), 0]):0);
}
else if (lessThan(absMax, Math.pow(10,8))) {
digits = (numberPos? d3.max([Math.ceil(Number.isInteger(Math.log10(numberPos/Math.pow(10,5)))? Math.log10(numberPos/Math.pow(10,5)) + 1:Math.log10(numberPos/Math.pow(10,5))), 0]):0);
}
else if (lessThan(absMax, Math.pow(10,9))) {
digits = (numberPos? d3.max([Math.floor(Math.log10(numberPos/Math.pow(10,5))), 0]):0);
}
else if (lessThan(absMax, Math.pow(10,11))) {
digits = (numberPos? d3.max([Math.ceil(Number.isInteger(Math.log10(numberPos/Math.pow(10,8)))? Math.log10(numberPos/Math.pow(10,8)) + 1:Math.log10(numberPos/Math.pow(10,8))), 0]):0);
}
else if (lessThan(absMax, Math.pow(10,12))) {
digits = (numberPos? d3.max([Math.floor(Math.log10(numberPos/Math.pow(10,8))), 0]):0);
}
else {
digits = 3;
}
numberFormat = `,.${digits}s`;
//formatZero = d3.formatPrefix(numberFormat, absMax)
}
const formatted = `${d3.format(numberFormat)(d).replace(/G/,"B")}`;
return numberPos <= 0.01? d3.format(formatZero)(d): formatted
}
const makePrediction = (data) => {
const lastDataPointDate = data[data.length-1].date;
const lastDataPointValue = data[data.length-1].value;
const startOfLastPeriod = getPeriodStartFromDate[periodType](lastDataPointDate);
const endOfLastPeriod = getPeriodEndFromDate[periodType](lastDataPointDate);
const dateToday = new Date();
const daysDiff = d3.timeDay.count(dateToday, endOfLastPeriod);
const daysInPeriod = d3.timeDay.count(startOfLastPeriod, endOfLastPeriod);
const daysElapsedInPeriod = daysInPeriod + 1 - daysDiff;
return (daysDiff > 0) && (typeof(lastDataPointValue) == "number")? ((lastDataPointValue/daysElapsedInPeriod)*(daysDiff)) + lastDataPointValue: undefined;
}
const getDateDimensionIndex = (dimensionIds, possiblePeriods) => {
const intersection = d3.intersection(dimensionIds, possiblePeriods);
const period = [...intersection][0];
return dimensionIds.indexOf(period)
}
const itabular_remapped = itabular_remap(itabular);
const dimensionIds = itabular_remapped.Dimensions.map(d => d.Id);
const dimensionNames = itabular_remapped.Dimensions.map(d => d.Name);
const itabularSource = dimensionNames.every(d => d == null)? "Displayr": "Factbase"
const multiSeries = dimensionIds.includes("series");
const values = multiSeries? itabular_remapped.DataPoints[0].map(array => array.map(x => x.Value)): itabular_remapped.DataPoints[0].flat().map(x => x.Value);
const dateDimensionIndex = getDateDimensionIndex(dimensionIds, possiblePeriods);
const dates = itabular_remapped.Dimensions[dateDimensionIndex].Elements.map(d => new Date(d.Name) );
const columnsData = multiSeries? values.map((d,i) => d.concat([dates[i]])): values.map((d,i) => [d,dates[i]]);
const series = itabularSource == "Factbase"? multiSeries ? dimensionIds.filter(d => d == "series")[0].Elements.map(d => d.Name): ["undefined"] : itabular_remapped.Dimensions[0].Elements.map(d => d.Name? d.Name: "series")
const columns = series.concat(['date']);
const dataForArquero = columnsData.map(
(c,i) => Object.fromEntries(c.map((x,index) => [columns[index], x]))
);
const output = aq.from(dataForArquero)
.fold(aq.not("date"))
.orderby('key','date')
.derive({
value: aq.escape( d => isNaN(d.value)? undefined: d.value ) })
.objects();
const prediction = makePrediction(output);
const allValues = output.map(d => d.value);
const allTruthyNonPredictionValues = allValues.filter(d => d)
allValues.push(prediction);
const rangeNew = d3.extent(allValues);
const endLabels = [formatY(rangeNew, allTruthyNonPredictionValues[allTruthyNonPredictionValues.length-1]), prediction? formatY(rangeNew, prediction): undefined].filter(d => d)
return {
yExtent: rangeNew,
endLabels: endLabels
} */
}