Public
Edited
Feb 5, 2024
1 fork
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
display = displayDR ? generateForm() : md`__view disabled__`
Insert cell
Insert cell
//pcpVis
Insert cell
//rcVis
Insert cell
//cppABBCDataVis
Insert cell
//cppABCDDataVis
Insert cell
border = +borderThickness + "px solid black"
Insert cell
customMargin2 = file != null && importData.margintop != undefined
? {
top: importData.margintop,
left: importData.marginleft,
bottom: importData.marginbottom,
right: importData.marginright
}
: { top: 40, left: 40, bottom: 40, right: 40 }
Insert cell
scaleLog = false
Insert cell
globalFontSize = 14
Insert cell
mixmultiplyValue = 0.3
Insert cell
globalOpacity = 1

Insert cell
marginValues = [40, 50, 60, 16]
Insert cell
ticks = [8, 6, 5, 3]
Insert cell
rcticks = [8, 6, 3, 3]
Insert cell
Insert cell
logT = 1e-6
Insert cell
backup = ["#4e79a7", "#e15759", "#59a14f", "#f28e2c", "#76b7b2"]
Insert cell
colorValues = ["#27a13c", "#f28e2c", "#76b7b2", "#4296f4", "#EA4335"]
Insert cell
update()
Insert cell
function generateForm() {
const containingF = html`<form>`;
const f = html`<div>`;
update();
d3.select(f).attr("position", "absolute").style("zoom", 0.5);
f.append(viewof classes);
f.append(md`# Quantitative approaches:`);
f.append(
md`### <pre> PCP radar chart Cyclic Polygons (${rawData.type})`
);
f.append(pcpVis);
f.append(rcVis);
f.append(cppVis);
f.append(viewof rawData);
return f;
}
Insert cell
function generateDimensionalityReductionForm() {
const containingF = html`<form>`;
const f = html`<div>`;
update();
d3.select(f).attr("position", "absolute").style("zoom", 0.5);
f.append(md`# Dimensionality reduction approaches:`);
f.append(
md`t-SNE: ${tsneDone ? "done." : "working..."} <br> UMAP: ${
umapDone ? "done." : "working..."
}`
);
f.append(md`### t-SNE & UMAP:`);

f.append(
md`### <pre> t-SNE t-SNE + our glyphs UMAP UMAP + our glyphs`
);
f.append(tsneVis);
f.append(cppTSNEvis);
f.append(umapVis);
f.append(cppUMAPvis);
f.append(md`### Our placement:`);
f.append(
md`### <pre> intrinsic geometric angular statistical`
);
f.append(cppAvgVis);
f.append(cppGeoVis);
f.append(cppAngVis);
f.append(cppStatVis);
return f;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
viewof classes = {
if (Array.from(new Set(currentObject.colorMapping)).length > 10) {
return md`#### Too many classes.`;
} else {
return legend({
color: currentObject.colorScale,
title: "Classes"
});
}
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
f = d3.scaleLog([4, 10], [90, 100]).clamp(1)
Insert cell
f(4.4)
Insert cell
rcVis.parentNode
Insert cell
Insert cell
Insert cell
Insert cell
cppVis.parentNode
Insert cell
cppVis = {
const scale = 0.05;

function calculatePieceWisePathRight(d, i) {
const path = d3.path();
const x0 = scales.polygonScaleX(d.x0),
y0 = scales.polygonScaleY(d.y0),
x1 = scales.polygonScaleX(d.x1),
y1 = scales.polygonScaleY(d.y1);
path.moveTo(x0, y0);
path.lineTo(x1, y1);
return path;
}

const cpp = d3.select(DOM.element("div"));
const svg = cpp
.append("svg")
.attr("viewBox", [0, 0, dim, dim])
.style("background", "white")
.attr("overflow", "hidden")
.attr("width", 500)
.style("border", border);

svg.append("g").attr("id", "axisX").attr("class", "axes");
svg.append("g").attr("id", "axisY").attr("class", "axes");

svg.append("g").attr("id", "all");
const linecontainer = svg.append("g").style("mix-blend-mode", "darken");

linecontainer.append("g").attr("id", "doubleCircles");
linecontainer.append("g").attr("id", "tripleCircles");
var scales = {};
scales.polygonScaleX = d3.scaleLinear(
[0, 1],
[margin.left, dim - margin.right]
);
scales.polygonScaleXleft = d3.scaleLinear(
[0, 1],
[margin.left, dim - margin.right]
);
scales.polygonScaleY = d3.scaleLinear(
[0, 1],
[dim - margin.bottom, margin.top]
);
scales.displayScaleX = d3.scaleLinear(d3.extent(extentX), [
margin.left,
dim - margin.right
]);
scales.displayScaleY = d3.scaleLinear(d3.extent(extentY), [
dim - margin.bottom,
margin.top
]);
if (scaleLog && d3.extent(extentX)[1] > 0) {
scales.displayScaleX = d3
.scaleLog(
[
d3.extent(extentX)[0] < logT ? logT : d3.extent(extentX)[0],
d3.extent(extentX)[1]
],
[margin.left, dim - margin.right]
)
.clamp(logT);
scales.displayScaleY = d3
.scaleLog(
[
d3.extent(extentY)[0] < logT ? logT : d3.extent(extentY)[0],
d3.extent(extentY)[1]
],
[dim - margin.bottom, margin.top]
)
.clamp(logT);
}
scales.arrowScale = d3.scaleLinear(
d3.extent(currentObject.sideLengths.map((d) => d[0])),
[0.1, 1]
);
svg
.select("#axisX")
.attr("class", "axes")
.attr("transform", `translate(${0} ${dim - margin.bottom})`)
.call(
d3
.axisBottom(scales.displayScaleX)
.tickFormat(tickFormat)
.ticks(ticks[tickSizes[tickSize] - 1])
.tickSizeInner(innerTickSize)
)
.attr("stroke-width", axisWidth)
.selectAll("text")
.attr("font-size", globalFontSize)
.attr("y", yDistance);
svg
.select("#axisY")
.attr("transform", `translate(${margin.left} 0)`)
.call(
d3
.axisLeft(scales.displayScaleY)
.tickFormat(tickFormat)
.ticks(ticks[tickSizes[tickSize] - 1])
.tickSizeInner(innerTickSize)
)
.attr("stroke-width", axisWidth)
.selectAll("text")
.attr("font-size", globalFontSize)
.attr("x", xDistance);
//.attr("font-size", tickSizes[tickSize] * 8);
const drawCircle = data.map((d) =>
d.map((b, i) => {
for (var j = i + 1; j < d.length; ++j) {
if (d[i].x0 == d[j].x0 && d[i].y0 == d[j].y0) return true;
}
return false;
})
);
svg
.select("#doubleCircles")
.selectAll("g")
.data(data)
.join("g")
.selectAll("circle")
.data((d) => d)
.join("circle")
.attr("r", (d, i) => (i == 0 ? pointSize : pointSize))
.attr("cx", (d) => scales.polygonScaleX(d.x0))
.attr("cy", (d) => scales.polygonScaleY(d.y0))
.attr("opacity", (d, i) => (drawCircle[d.index][i] ? 1 : 0))
.attr("fill", (d, i) => {
return enableSubselection
? selectedSamples.map((d) => d[1].value).includes(d.index)
? currentObject.colorScale(currentObject.colorMapping[d.index])
: "none"
: currentObject.colorScale(currentObject.colorMapping[d.index]);
})
.style("mix-blend-mode", "multiply");
svg
.select("#tripleCircles")
.selectAll("g")
.data(data)
.join("g")
.selectAll("circle")
.data((d) => d)
.join("circle")
.attr("r", (d, i) => (i == 0 ? pointSize : pointSize))
.attr("cx", (d) => scales.polygonScaleX(d.x0))
.attr("cy", (d) => scales.polygonScaleY(d.y0))
.attr("opacity", (d, i) => (drawCircle[d.index][i] ? 1 : 0))
.attr("fill", (d, i) => {
return enableSubselection
? selectedSamples.map((d) => d[1].value).includes(d.index)
? currentObject.colorScale(currentObject.colorMapping[d.index])
: "none"
: currentObject.colorScale(currentObject.colorMapping[d.index]);
})
.style("mix-blend-mode", "multiply");
const composite2 = linecontainer
.append("g")
.selectAll("g")
.data(data)
.join("g")
.style("mix-blend-mode", "multiply")
.attr("opacity", (_, i) => mixmultiplyValue)
.selectAll("g")

.data((d) => d)
.join("g");
const composite1 = linecontainer
.append("g")
.selectAll("g")
.data(data)
.join("g")
.style("mix-blend-mode", blendmode)
.attr("opacity", (_, i) => globalOpacity)
.selectAll("g")

.data((d) => d)
.join("g");

composite1
.append("path")

.attr("fill", (d) =>
enableSubselection
? selectedSamples.map((d) => d[1].value).includes(d.index)
? currentObject.colorScale(currentObject.colorMapping[d.index])
: "none"
: currentObject.colorScale(currentObject.colorMapping[d.index])
)
.attr("class", "dataline");

composite1
.append("path")
.attr("stroke-linecap", "round")
.attr("d", calculatePieceWisePathRight)
.attr("class", "dataline")
.attr("fill", "none")
.attr("stroke", (d, i) => {
return enableSubselection
? selectedSamples.map((d) => d[1].value).includes(d.index)
? currentObject.colorScale(currentObject.colorMapping[d.index])
: "none"
: currentObject.colorScale(currentObject.colorMapping[d.index]);
})
.attr("stroke-width", (_, i) => lineThickness);

composite1
.append("circle")
.attr("r", (d, i) => (i == 0 ? firstScaler * pointSize : pointSize))
.attr("cx", (d) => scales.polygonScaleX(d.x0))
.attr("cy", (d) => scales.polygonScaleY(d.y0))
.attr("opacity", (d, i) => 1)
// .attr("stroke-width", 0.1)
// .attr("stroke", (d, i) => (i == 0 ? "black" : ""))
.attr("fill", (d, i) => {
return enableSubselection
? selectedSamples.map((d) => d[1].value).includes(d.index)
? currentObject.colorScale(currentObject.colorMapping[d.index])
: "none"
: currentObject.colorScale(currentObject.colorMapping[d.index]);
});
composite2
.append("path")
.attr("fill", (d) =>
enableSubselection
? selectedSamples.map((d) => d[1].value).includes(d.index)
? currentObject.colorScale(currentObject.colorMapping[d.index])
: "none"
: currentObject.colorScale(currentObject.colorMapping[d.index])
)
.attr("class", "dataline")
.attr(
"stroke",
(d, i) =>
enableSubselection
? selectedSamples.map((d) => d[1].value).includes(d.index)
? "none" //"black"
: "none"
: "none" //"black"
);

composite2
.append("path")
.attr("stroke-linecap", "round")
.attr("d", calculatePieceWisePathRight)
.attr("class", "dataline")
.attr("fill", "none")
.attr("stroke", (d, i) => {
return enableSubselection
? selectedSamples.map((d) => d[1].value).includes(d.index)
? currentObject.colorScale(currentObject.colorMapping[d.index])
: "none"
: currentObject.colorScale(currentObject.colorMapping[d.index]);
})
.attr("stroke-width", (_, i) => lineThickness);

composite2
.append("circle")
.attr("r", (d, i) => (i == 0 ? firstScaler * pointSize : pointSize))
.attr("cx", (d) => scales.polygonScaleX(d.x0))
.attr("cy", (d) => scales.polygonScaleY(d.y0))
.attr("opacity", (d, i) => 1)
.attr("fill", (d, i) => {
return enableSubselection
? selectedSamples.map((d) => d[1].value).includes(d.index)
? currentObject.colorScale(currentObject.colorMapping[d.index])
: "none"
: currentObject.colorScale(currentObject.colorMapping[d.index]);
});
return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
mutable tsneDone = false
Insert cell
tsneEMB = tsne()
Insert cell
mutable tsneIteration = 0
Insert cell
async function* tsne() {
if (DRStop) {
return;
}
const stepsize = 1;
const tnsePerplexity = 30;
mutable tsneIteration = 0;
const model = new tsnejs.tSNE({
dim: 2,
perplexity: perplexity,
epsilon: 10
});
model.initDataDist(distances);
mutable tsneDone = false;
var cost = 100,
cost0 = 0;
while (Math.abs(cost - cost0) > 1e-5) {
cost = cost0;
cost0 = cost * 0.95 + 0.05 * model.step();
mutable tsneIteration++;
yield await model.getSolution();
}
mutable tsneDone = true;
}
Insert cell
Insert cell
Insert cell
mutable umapDone = false
Insert cell
async function* umap() {
if (DRStop) {
return;
}
mutable umapIteration = 0;
mutable umapDone = false;
const nNeighbors = nNeigh;
const umapdata = currentObject.dataset.map((d) => Object.values(d));
const stepsize = 1;
const umap = new UMAP({
minDist: 0.1,
nNeighbors:
nNeighbors < currentObject.dataset.length - 1
? nNeighbors
: currentObject.dataset.length - 1
});
const nEpochs = umap.initializeFit(umapdata);
for (let i = 0; i < nEpochs; i++) {
umap.step();
mutable umapIteration++;
yield await umap.getEmbedding();
}
mutable umapDone = true;
}
Insert cell
umapEMB = umap()
Insert cell
mutable umapIteration = 0
Insert cell
Insert cell
Insert cell
cppTSNEvis = {
if (!displayDR) return d3.create("svg");
const scale = 0.05;
const embedding = DRStop
? Array.from({ length: currentObject.dataset.length }, (_, i) => [0, 0])
: tsneEMB;
function calculatePieceWisePathLeft(d, i) {
const path = d3.path();
const x0 =
(1 - scale) *
scales.placementScaleX(embedding.map((d) => d[0])[d.index]) +
scale * scales.polygonScaleXleft(d.x0),
y0 =
(1 - scale) *
scales.placementScaleY(embedding.map((d) => d[1])[d.index]) +
scale * scales.polygonScaleY(d.y0),
x1 =
(1 - scale) *
scales.placementScaleX(embedding.map((d) => d[0])[d.index]) +
scale * scales.polygonScaleXleft(d.x1),
y1 =
(1 - scale) *
scales.placementScaleY(embedding.map((d) => d[1])[d.index]) +
scale * scales.polygonScaleY(d.y1);
path.moveTo(x0, y0);
path.lineTo(x1, y1);
return path;
}
const cppUMAP = d3.select(DOM.element("div"));
const svg = cppUMAP
.append("svg")
.attr("viewBox", [0, 0, dim, dim])
.style("background", "white")
.attr("overflow", "hidden")
.attr("width", 500);
const lineContainer = svg.append("g").style("mix-blend-mode", "multiply");
lineContainer.append("g").attr("id", "placementPolygons2");
lineContainer.append("g").attr("id", "placementPolygons1");
svg.append("g").attr("id", "axisPlacementX").attr("class", "axes");
svg.append("g").attr("id", "axisPlacementY").attr("class", "axes");
var scales = {};
scales.color = d3.interpolateBlues;
scales.placementScaleX = d3
.scaleLinear(d3.extent(embedding.map((d) => d[0])), [
margin.left,
dim - margin.right
])
.nice();
scales.placementScaleY = d3
.scaleLinear(d3.extent(embedding.map((d) => d[1])), [
dim - margin.top,
margin.bottom
])
.nice();
scales.polygonScaleX = d3
.scaleLinear([0, 1], [margin.left, dim - margin.right])
.nice();
scales.polygonScaleXleft = d3
.scaleLinear([0, 1], [margin.left, dim - margin.right])
.nice();
scales.polygonScaleY = d3
.scaleLinear([0, 1], [dim - margin.top, margin.bottom])
.nice();
scales.displayScaleX = d3
.scaleLinear(d3.extent(extentX), [margin.left, dim - margin.right])
.nice();
scales.displayScaleY = d3
.scaleLinear(d3.extent(extentY), [dim - margin.top, margin.bottom])
.nice();
scales.arrowScale = d3.scaleLinear(
d3.extent(currentObject.sideLengths.map((d) => d[0])),
[0, 1]
);
svg
.select("#axisPlacementX")
.attr("class", "axes")
.attr("transform", `translate(0 ${dim - margin.top})`)
.call(
d3
.axisBottom(scales.placementScaleX.nice())
.tickFormat(tickFormat)
.ticks(ticks[tickSizes[tickSize] - 1])
)
.attr("stroke-width", axisWidth)
.selectAll("text")
.attr("font-size", globalFontSize)
.attr("y", yDistance);
svg
.select("#axisPlacementY")
.attr("class", "axes")
.attr("transform", `translate(${margin.left} 0)`)
.call(
d3
.axisLeft(scales.placementScaleY.nice())
.tickFormat(tickFormat)
.ticks(ticks[tickSizes[tickSize] - 1])
)
.attr("stroke-width", axisWidth)
.selectAll("text")
.attr("font-size", globalFontSize)
.attr("x", xDistance);
svg
.select("#placementPolygons1")
.selectAll("g")
.data(data)
.join("g")
.style("mix-blend-mode", blendmode)
.selectAll("path")
.data((d) => d)
.join("path")
.attr("opacity", (_, i) => 1)
.attr("stroke-linecap", "round")
.attr("d", calculatePieceWisePathLeft)
.attr("class", "dataglyph")
.attr("fill", "none")
.attr("stroke", (d, i) => {
return enableSubselection
? selectedSamples.map((d) => d[1].value).includes(d.index)
? currentObject.colorScale(currentObject.colorMapping[d.index])
: "none"
: currentObject.colorScale(currentObject.colorMapping[d.index]);
})
.attr("stroke-width", (_, i) => tickSizes[tickSize] * 1)
.attr("opacity", 1);
svg
.select("#placementPolygons2")
.selectAll("g")
.data(data)
.join("g")
.style("mix-blend-mode", "multiply")
.selectAll("path")
.data((d) => d)
.join("path")
.attr("opacity", (_, i) => 1)
.attr("stroke-linecap", "round")
.attr("d", calculatePieceWisePathLeft)
.attr("class", "dataglyph")
.attr("fill", "none")
.attr("stroke", (d, i) => {
return enableSubselection
? selectedSamples.map((d) => d[1].value).includes(d.index)
? currentObject.colorScale(currentObject.colorMapping[d.index])
: "none"
: currentObject.colorScale(currentObject.colorMapping[d.index]);
})
.attr("stroke-width", (_, i) => tickSizes[tickSize] * 1)
.attr("opacity", 0.3);
return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
d3.extent(
currentObject.dataset.map((d) =>
d3.mean(
Object.values(d).filter((b, i) =>
Object.values(d).length % 2 == 0 ? i % 2 == 1 : i % 2 == 1 || i == 0
)
)
)
)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
data = currentObject[rawData.data].map((d, j) =>
d.map((b, i) => ({
x0: b.x,
y0: b.y,
x1: d[(i + 1) % d.length].x,
y1: d[(i + 1) % d.length].y,
index: j
}))
)
Insert cell
Insert cell
clusterDistributions = Array.from(
{ length: customCreationType == "Distribution" ? clusterCount : 0 },
(_, i) => {
return Inputs.radio(
[
"randomUniform",
"randomNormal",
"randomLogNormal",
"randomExponential",
"randomPoisson",
"randomBinomial",
"randomPareto"
],
{
label: "Distr. cluster " + i + ":",
value:
file != null
? importData.clusterDistributionValues[i]
: "randomUniform"
}
);
}
)
Insert cell
distributionParameters = customCreationType == "Custom values"
? []
: [
[
"Normal (mu and sigma)",
"randomNormal",
Inputs.range([0, 10], {
label: "Mu:",
step: 0.1,
value:
file != null && importData.distributionParameters.length > 0
? importData.distributionParameters[0][0]
: 0.4
}),
Inputs.range([0, 1], {
label: "Sigma:",
step: 0.1,
value:
file != null && importData.distributionParameters.length > 0
? importData.distributionParameters[0][1]
: 0.1
})
],
[
"Log-Normal (mu and sigma)",
"randomLogNormal",
Inputs.range([-1, 1], {
label: "Mu:",
step: 0.001,
value:
file != null && importData.distributionParameters.length > 0
? importData.distributionParameters[1][0]
: 0.5
}),
Inputs.range([0, 1], {
label: "Sigma:",
step: 0.001,
value:
file != null && importData.distributionParameters.length > 0
? importData.distributionParameters[1][1]
: 0.4
})
],
[
"Exponential (lambda)",
"randomExponential",
Inputs.range([0.2, 10], {
label: "Lambda:",
step: 0.001,
value:
file != null && importData.distributionParameters.length > 0
? importData.distributionParameters[2][0]
: 2
})
],
[
"Poisson log_10(lambda)",
"randomPoisson",
Inputs.range([-1, 12], {
label: "Lambda:",
step: 0.001,
value:
file != null && importData.distributionParameters.length > 0
? importData.distributionParameters[3][0]
: 2.6
})
],
[
"Binomial (n,p)",
"randomBinomial",
Inputs.range([1, 100], {
label: "n:",
step: 1,
value:
file != null && importData.distributionParameters.length > 0
? importData.distributionParameters[4][0]
: 100
}),
Inputs.range([0, 1], {
label: "p:",
step: 0.001,
value:
file != null && importData.distributionParameters.length > 0
? importData.distributionParameters[4][1]
: 0.5
})
],
[
"Pareto alpha",
"randomPareto",
Inputs.range([0, 10], {
label: "alpha:",
step: 0.001,
value:
file != null && importData.distributionParameters.length > 0
? importData.distributionParameters[5][0]
: 1.16
})
]
]
Insert cell
Insert cell
Insert cell
importData = file != null ? file.json() : ""
Insert cell
distrparameters.map((d) => d.map((b) => b.value).filter((_, i) => i != 0))
Insert cell
customObject = {
var object = {};
const n = datasetLength;
const clusterIndices = linspace([0, datasetLength], clusterCount + 1);
var currentCluster = 1;

var raw = Array.from({
length:
customCreationType == "Distribution"
? datasetLength
: customValues.length < datasetLength
? customValues.length
: datasetLength
}).map((d, i) => {
var vectorObject = {};
for (var j = 0; j < datasetDim; ++j) {
if (i == Math.round(clusterIndices[currentCluster])) currentCluster++;
if (i <= Math.round(clusterIndices[currentCluster])) {
if (customCreationType == "Distribution") {
const dp = distrparameters.map((d) =>
d.map((b) => b.value).filter((_, i) => i != 0)
);
const normalMu = dp[0][0];
const normalSigma = dp[0][1];
const lognormalMu = dp[1][0];
const lognormalSigma = dp[1][1];
const expLambda = dp[2][0];
const poissonLambda = dp[3][0];
const binomialN = dp[4][0];
const binomialP = dp[4][1];
const paretoAlpha = dp[5][0];
const expr = clusterDistributionValues[currentCluster - 1];
switch (expr) {
case "randomUniform":
vectorObject[j] = d3.randomUniform()();
break;
case "randomNormal":
vectorObject[j] = d3.randomNormal(normalMu, normalSigma)();
break;
case "randomLogNormal":
vectorObject[j] = d3.randomLogNormal(
lognormalMu,
lognormalSigma
)();
break;
case "randomExponential":
vectorObject[j] = d3.randomExponential(expLambda)();
break;
case "randomPoisson":
vectorObject[j] = d3.randomPoisson(poissonLambda)();
break;
case "randomBinomial":
vectorObject[j] = d3.randomBinomial(binomialN, binomialP)();
break;
case "randomPareto":
vectorObject[j] = d3.randomPareto(paretoAlpha)();
break;
default:
vectorObject[j] = d3.randomUniform()();
break;
}
} else {
const val = customValues.map((d) => d.map((b) => b.value));
vectorObject[j] = val[i][j];
}
}
}
vectorObject["name"] = "Cluster " + currentCluster;
return vectorObject;
});
raw.columns = Object.keys(raw[0]);
object.data = raw;
object.nameColumn = "name";
object.description = "Custom Object.";
return object;
}
Insert cell
Insert cell
currentObject = {
// requires THREE.js in notebook via require("three@0.99.0/build/three.min.js")

// Helper functions:

// Statistical parameters:
function calculateMean(data) {
var arr = [];
const l = data.length;
for (var i = 0; i < l; ++i) {
const temp = Object.values(data[i]);
arr[i] = d3.mean(temp);
}
return arr;
}
function calculateStandardDeviation(data) {
var arr = [];
const l = data.length;
for (var i = 0; i < l; ++i) {
const dP = Object.values(data[i]),
mean = d3.mean(dP);
arr[i] = Math.sqrt(d3.sum(dP.map((d) => (d - mean) ** 2)) / dP.length);
}
return arr;
}

function calculateSkewness(data) {
var arr = [];
const l = data.length;
const means = calculateMean(data);
const sDs = calculateStandardDeviation(data);
for (var i = 0; i < l; ++i) {
const dP = Object.values(data[i]);
arr[i] =
d3.sum(dP.map((d) => (d - means[i]) ** 3 / dP.length)) / sDs[i] ** 3;
}
return arr;
}

function calculateKurtosis(data) {
var arr = [];
const l = data.length;
const means = calculateMean(data);
const sDs = calculateStandardDeviation(data);
for (var i = 0; i < l; ++i) {
const dP = Object.values(data[i]);
arr[i] =
d3.sum(dP.map((d) => (d - means[i]) ** 4 / dP.length)) / sDs[i] ** 4;
}
return arr;
}

// Geometric parameters:
function calculateLength(d) {
d = Object.values(d);
var size = d.length;
var l = {};
for (var i = 0; i < size; ++i) {
l[i] = Math.sqrt(
(d[i] - d[(i + 1) % size]) ** 2 +
(d[(i + 1) % size] - d[(i + 2) % size]) ** 2
);
}
return l;
}
function calculateDistance(v1, v2) {
return Math.sqrt((v2.x - v1.x) ** 2 + (v2.y - v1.y) ** 2);
}
function calculateBoundingSphere(d) {
d = Object.values(d);
var s = d.length;
const geometry = new THREE.Geometry();
for (var j = 0; j < s; ++j) {
var temp = new THREE.Vector3(d[j], d[(j + 1) % s], 0);
geometry.vertices.push(temp);
}
geometry.computeBoundingSphere();
return geometry.boundingSphere;
}

function calculateArea(d) {
var sum = 0;
const l = d.length;
for (var i = 0; i < l; ++i) {
sum += d[i].x * d[(i + 1) % l].y;
sum -= d[i].y * d[(i + 1) % l].x;
}
return 0.5 * Math.abs(sum);
}

function calculateCircumference(d) {
var sum = 0;
const l = d.length;
for (var i = 0; i < l; ++i) {
sum += Math.sqrt(
(d[(i + 1) % l].x - d[i].x) ** 2 + (d[(i + 1) % l].y - d[i].y) ** 2
);
}
return sum;
}

function calculateEdgeLengths(d) {
const arr = [];
const l = d.length;
for (var i = 0; i < l; ++i) {
arr.push(
Math.sqrt(
(d[(i + 1) % l].x - d[i].x) ** 2 + (d[(i + 1) % l].y - d[i].y) ** 2
)
);
}
return arr;
}

function calculateAngles(d) {
var angles = [];
for (var i = 0; i < d.length; ++i) {
const v1x = d[(i - 1 + d.length) % d.length].x - d[i].x;
const v1y = d[(i - 1 + d.length) % d.length].y - d[i].y;
const v2x = d[(i + 1) % d.length].x - d[i].x;
const v2y = d[(i + 1) % d.length].y - d[i].y;
const angle =
((Math.atan2(v2y, v2x) - Math.atan2(v1y, v1x)) / Math.PI) * 180;
angle > 180
? angles.push(angle - 360)
: angle < -180
? angles.push(angle + 360)
: angles.push(parseFloat(angle));
}
return angles;
}

function calculateAbsoluteAngles(d) {
var angles = [];
for (var i = 0; i < d.length; ++i) {
const v1x = d[(i - 1 + d.length) % d.length].x - d[i].x;
const v1y = d[(i - 1 + d.length) % d.length].y - d[i].y;
const v2x = d[(i + 1) % d.length].x - d[i].x;
const v2y = d[(i + 1) % d.length].y - d[i].y;
const angle =
((Math.atan2(v2y, v2x) - Math.atan2(v1y, v1x)) / Math.PI) * 180;
angle > 0 ? angles.push(angle) : angles.push(angle + 360);
}
return angles;
}

// Dataset properties:

var object = {};
const selectedDataset = customDataset ? customObject : datasets[choice];
var raw = selectedDataset.data;
// Extract Name Column and clean dataset:
const colorMapping = raw.map((d) => d[selectedDataset.nameColumn]);
const colorCategories = [...new Set(colorMapping)];
const colorScale = d3.scaleOrdinal(colorCategories, colorValues);

const dataCategories = raw.columns.filter(
(d) => d != selectedDataset.nameColumn
);
const dataset = raw.map((d) => {
var b = {};
var incomplete = false;
for (var i = 0; i < dataCategories.length; ++i) {
b[dataCategories[i]] = d[dataCategories[i]];
if (d[dataCategories[i]] == null) {
b[dataCategories[i]] = 0;
}
}
return b;
});
dataset.columns = dataCategories;

const sideLengths = dataset.map((d) => calculateLength(d));

const minMaxAvg = dataset.map((d) => {
var min = Number.MAX_VALUE;
var max = -Number.MAX_VALUE;
var sum = 0;
for (var de in d) {
d[de] > max ? (max = d[de]) : "foo";
d[de] < min ? (min = d[de]) : "foo";
sum += d[de];
}
return { min: min, max: max, avg: sum / Object.keys(d).length };
});

const dimScales = new Map(
Array.from(dataCategories, (key) => [
key,
d3.scaleLinear(
d3.extent(dataset, (d) => d[key]),
[0, 1]
)
])
);

const ABCDpairs = dataset.map((d) => {
d = Object.values(d);
var s = d.length;
var pairArray = [];
for (var i = 0; i < s; i = i + 2) {
pairArray.push({ x: d[i], y: d[(i + 1) % s] });
}
return pairArray;
});

const ABCDavg = ABCDpairs.map((d) => ({
x: d3.sum(d.map((b) => b.x)) / d.length,
y: d3.sum(d.map((b) => b.y)) / d.length
}));

const zero = 0;
const fullExt = d3.extent(dataset.flatMap((d) => Object.values(d)));
var fullScale = d3.scaleLinear(fullExt, [zero, 1]);

const ABCDxExtent = d3.extent(
dataset.flatMap((d) => Object.values(d).filter((b, i) => i % 2 == 0))
);
const ABCDyExtent = d3.extent(
dataset.flatMap((d) =>
Object.values(d).filter((b, i) =>
Object.values(d).length % 2 == 1 ? i == 0 || i % 2 == 1 : i % 2 == 1
)
)
);
var ABCDscaleX = d3.scaleLinear(ABCDxExtent, [zero, 1]);
var ABCDscaleY = d3.scaleLinear(ABCDyExtent, [zero, 1]);

if (scaleLog && fullExt[1] > 0) {
fullScale = d3
.scaleLog([fullExt[0] < logT ? logT : fullExt[0], fullExt[1]], [zero, 1])
.clamp(logT);
ABCDscaleX = d3
.scaleLog(
[ABCDxExtent[0] < logT ? logT : ABCDxExtent[0], ABCDxExtent[1]],
[zero, 1]
)
.clamp(logT);
ABCDscaleY = d3
.scaleLog(
[ABCDyExtent[0] < logT ? logT : ABCDyExtent[0], ABCDyExtent[1]],
[zero, 1]
)
.clamp(logT);
}
const ABCDpairsDimensionNormalized = dataset.map((d) => {
d = Object.values(d);
var s = d.length;
var pairArray = [];
for (var i = 0; i < s; i = i + 2) {
pairArray.push({
x: dimScales.get(dataCategories[i])(d[i]),
y: dimScales.get(dataCategories[(i + 1) % s])(d[(i + 1) % s])
});
}
return pairArray;
});

const ABCDpairsDatasetNormalized = dataset.map((d) => {
d = Object.values(d);
var s = d.length;
var pairArray = [];
for (var i = 0; i < s; i = i + 2) {
pairArray.push({ x: ABCDscaleX(d[i]), y: ABCDscaleY(d[(i + 1) % s]) });
}
return pairArray;
});
const ABBCpairsDimensionNormalized = dataset.map((d) => {
d = Object.values(d);
var s = d.length;
var pairArray = [];
for (var i = 0; i < s; ++i) {
pairArray.push({
x: dimScales.get(dataCategories[i])(d[i]),
y: dimScales.get(dataCategories[(i + 1) % s])(d[(i + 1) % s])
});
}
return pairArray;
});
const ABBCpairsDatasetNormalized = dataset.map((d) => {
d = Object.values(d);
var s = d.length;
var pairArray = [];
for (var i = 0; i < s; ++i) {
pairArray.push({ x: fullScale(d[i]), y: fullScale(d[(i + 1) % s]) });
}
return pairArray;
});

const boundingSphere = dataset.map((d) => calculateBoundingSphere(d));

const lrAnglesABBCDim = ABBCpairsDimensionNormalized.map((d) =>
calculateAngles(d)
);

const LRABBCDim = lrAnglesABBCDim.map((d) => {
var LR = { left: 0, right: 0 };
for (var i = 0; i < d.length; ++i) {
d[i] < 0 ? (LR.left += Math.abs(d[i])) : (LR.right += Math.abs(d[i]));
}
return LR;
});

const lrAnglesABBCData = ABBCpairsDatasetNormalized.map((d) =>
calculateAngles(d)
);

const LRABBCData = lrAnglesABBCData.map((d) => {
var LR = { left: 0, right: 0 };
for (var i = 0; i < d.length; ++i) {
d[i] < 0 ? (LR.left += Math.abs(d[i])) : (LR.right += Math.abs(d[i]));
}
return LR;
});

const lrAnglesABCDDim = ABCDpairsDimensionNormalized.map((d) =>
calculateAngles(d)
);

const LRABCDDim = lrAnglesABCDDim.map((d) => {
var LR = { left: 0, right: 0 };
for (var i = 0; i < d.length; ++i) {
d[i] < 0 ? (LR.left += Math.abs(d[i])) : (LR.right += Math.abs(d[i]));
}
return LR;
});

const lrAnglesABCDData = ABCDpairsDatasetNormalized.map((d) =>
calculateAngles(d)
);

const LRABCDData = lrAnglesABCDData.map((d) => {
var LR = { left: 0, right: 0 };
for (var i = 0; i < d.length; ++i) {
d[i] < 0 ? (LR.left += Math.abs(d[i])) : (LR.right += Math.abs(d[i]));
}
return LR;
});
selectedDataset.customValues = customValues.map((d) => d.map((b) => b.value));
selectedDataset.clusterDistributionValues = clusterDistributionValues;
selectedDataset.distributionParameters = distrparameters.map((d) =>
d.map((b) => b.value).filter((_, i) => i != 0)
);
selectedDataset.creationType = customCreationType;
selectedDataset.selectedSamples = 0;
selectedDataset.customLength = datasetLength;
selectedDataset.customDim = datasetDim;
selectedDataset.customClustercount = clusterCount;
selectedDataset.lineThickness = lineThickness;
selectedDataset.dotRadius = dotRadius;
selectedDataset.glyphThickness = glyphThickness;
selectedDataset.pointSize = pointSize;
selectedDataset.axisWidth = axisWidth;
selectedDataset.innerTickSize = innerTickSize;
selectedDataset.xDistance = xDistance;
selectedDataset.yDistance = yDistance;
selectedDataset.marginleft = margin.left;
selectedDataset.marginright = margin.right;
selectedDataset.margintop = margin.top;
selectedDataset.marginbottom = margin.bottom;

object.export = selectedDataset;
// Polygon Data:
object.ABBCdimensionPairs = ABBCpairsDimensionNormalized;
object.ABBCdatasetPairs = ABBCpairsDatasetNormalized;

object.ABCDdimensionPairs = ABCDpairsDimensionNormalized;
object.ABCDdatasetPairs = ABCDpairsDatasetNormalized;

// Placement Data:
object.angles = ABBCpairsDimensionNormalized.map((d) =>
calculateAbsoluteAngles(d)
);

object.leftanglesABBCDim = LRABBCDim.map((d) => d.left);
object.rightanglesABBCDim = LRABBCDim.map((d) => d.right);

object.leftanglesABBCData = LRABBCData.map((d) => d.left);
object.rightanglesABBCData = LRABBCData.map((d) => d.right);

object.leftanglesABCDDim = LRABCDDim.map((d) => d.left);
object.rightanglesABCDDim = LRABCDDim.map((d) => d.right);

object.leftanglesABCDData = LRABCDData.map((d) => d.left);
object.rightanglesABCDData = LRABCDData.map((d) => d.right);

object.areaABBCDim = ABBCpairsDimensionNormalized.map((d) =>
calculateArea(d)
);
object.areaABBCData = ABBCpairsDatasetNormalized.map((d) => calculateArea(d));
object.areaABCDDim = ABCDpairsDimensionNormalized.map((d) =>
calculateArea(d)
);
//object.areaABCDData = ABCDpairsDatasetNormalized.map((d) => calculateArea(d));
object.areaABCDData = ABCDpairsDatasetNormalized.map(
(d) => Math.round(calculateArea(d) * 10000) / 10000
);

object.circumferenceABBCDim = ABBCpairsDimensionNormalized.map((d) =>
calculateCircumference(d)
);
object.circumferenceABBCData = ABBCpairsDatasetNormalized.map((d) =>
calculateCircumference(d)
);
object.circumferenceABCDDim = ABCDpairsDimensionNormalized.map((d) =>
calculateCircumference(d)
);
object.circumferenceABCDData = ABCDpairsDatasetNormalized.map((d) =>
calculateCircumference(d)
);
object.sidelengthsABBCDim = ABBCpairsDimensionNormalized.map((d) =>
calculateEdgeLengths(d)
);
object.sidelengthsABBCData = ABBCpairsDatasetNormalized.map((d) =>
calculateEdgeLengths(d)
);
object.sidelengthsABCDDim = ABCDpairsDimensionNormalized.map((d) =>
calculateEdgeLengths(d)
);
object.sidelengthsABCDData = ABCDpairsDatasetNormalized.map((d) =>
calculateEdgeLengths(d)
);
object.circumference = sideLengths.map((d) => {
d = Object.values(d);
return d3.sum(d);
});
object.sideLengths = sideLengths;
object.pcpScales = Array.from(dataCategories, (key) => [
key,
d3.scaleLinear(
d3.extent(dataset, (d) => d[key]),
[dim - margin.top, margin.bottom]
)
]);
if (scaleLog) {
object.pcpScales = Array.from(dataCategories, (key) => [
key,
d3
.scaleLog(
[
d3.extent(dataset, (d) => d[key])[0] < logT
? logT
: d3.extent(dataset, (d) => d[key])[0],
d3.extent(dataset, (d) => d[key])[1]
],
[dim - margin.top, margin.bottom]
)
.clamp(true)
]);
}


object.min = minMaxAvg.map((d) => d.min);
object.max = minMaxAvg.map((d) => d.max);
object.avg = minMaxAvg.map((d) => d.avg);

object.ABCDavgXDim = ABCDpairsDimensionNormalized.map((d) =>
d3.mean(d.map((b) => b.x))
);
object.ABCDavgYDim = ABCDpairsDimensionNormalized.map((d) =>
d3.mean(d.map((b) => b.y))
);

object.ABCDavgXData = ABCDpairsDatasetNormalized.map((d) =>
d3.mean(d.map((b) => b.x))
);
object.ABCDavgYData = ABCDpairsDatasetNormalized.map((d) =>
d3.mean(d.map((b) => b.y))
);

object.kurtosis = calculateKurtosis(dataset);
object.skewness = calculateSkewness(dataset);
object.standarddeviation = calculateStandardDeviation(dataset);

// General Data:
object.dataset = dataset;
object.colorMapping = colorMapping;
object.colorScale = colorScale;

return object;
}
Insert cell
Insert cell
distances = currentObject.dataset
.map((d) => Object.values(d))
.map((a) =>
currentObject.dataset
.map((d) => Object.values(d))
.map((b) => euclidianDist(a, b))
)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more