Public
Edited
Sep 4, 2024
Insert cell
Insert cell
constant = 1
Insert cell
non_constant = Math.random()
Insert cell
derived = non_constant
Insert cell
non_constant_object = ({prop: non_constant})


Insert cell
Insert cell
curly_braces_variable = {
const y = constant == non_constant? 1: 2
return y
}
Insert cell
self_running_function = (() => {
const y = constant == non_constant? 1: 2
return y
})()
Insert cell
d3_test = d3.utcFormat("%Y")(new Date);
Insert cell
plot_test = Plot.plot({x: {type: "sqrt", domain: [0, 100], grid: true}}).baseURI
Insert cell
Insert cell
Insert cell
/** This should only be to obtain the yExtent of a single-series line chart.
* The single series line chart, when shown as part of a grid - needs a shared Y axis with minimum and maximum values that cater for minimums and maximums across all charts in the grid. The reason this is needed is that the prediction (as specified in FB-419) can go outside the natural bounds of the yExtent.
* This function works out the above.
* Can be called for example like this: getProcessedDataFeaturesSingleSeriesLineChart(itabular, ['day', 'week','month','quarter','year'], 'month')
* Returns the yExtent for this particular chart as an array of numbers [minimum, maximum]
* To work out the overall minimum and maximum for a single series chart grid, call this once for each chart in the grid set,
* and then take the minimum minimum and the maximum maximum.
* Note this function makes use of the moment.js and arquero libraries
*/
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
}
//transposing a 2D array: https://stackoverflow.com/questions/17428587/transposing-a-2d-array-in-javascript
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
} */
}
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more