function createLinePlot(
featureMaps,
featureMapKey,
originalData,
yMin = null,
yMax = null,
width = 150,
height = 150
) {
const { plotData, histogramData } = preprocess1DData(
featureMaps,
featureMapKey,
originalData
);
const featureName = featureMaps["1D"][featureMapKey].feature;
const className = featureMaps["1D"][featureMapKey].class;
const classIndex = featureMaps["1D"][featureMapKey].classIndex;
const score = featureMaps["1D"][featureMapKey].importance;
const xValues = data.map((d) => d[featureName]);
const minXValue = Math.min(...xValues);
const maxXValue = Math.max(...xValues);
if (yMin === null || yMax === null) {
const yValues = plotData.map((d) => d.y);
yMin = Math.min(...yValues);
yMax = Math.max(...yValues);
}
const linePlot = vl
.markBar()
.data(plotData)
.encode(
vl.color().value("black"),
vl.x().fieldO("x").title(featureName),
vl
.y()
.fieldQ("y")
.title("Output")
.scale({ domain: [yMin, yMax] }), // For y, use the y field and set the scale
vl.tooltip().fieldN("class") // For tooltips, show the class field
);
const numBins = 20;
// Calculate the bin size
const binSize = (maxXValue - minXValue) / numBins;
// Create an array of bin edges
const binEdges = Array.from(
{ length: numBins + 1 },
(_, i) => minXValue + i * binSize
);
// Determine the number of target classes
const targetClasses = [...new Set(originalData.map((d) => d.target))].sort(
(a, b) => a - b
);
// Create an object to store the bins for each class
const bins = targetClasses.reduce((acc, target) => {
acc[target] = binEdges.slice(0, -1).map((binStart, i) => ({
binStart,
binEnd: binEdges[i + 1],
count: 0,
class: target // Encode the target class in the bins' data
}));
return acc;
}, {});
// Iterate over the data and increment the count for the appropriate bin and class
originalData.forEach((d) => {
const binIndex = Math.floor((d[featureName] - minXValue) / binSize);
if (binIndex >= 0 && binIndex < numBins) {
bins[d.target][binIndex].count += 1;
}
});
// Find the maximum bin size
let maxBinSize = 0;
targetClasses.forEach((target) => {
bins[target].forEach((bin) => {
maxBinSize = Math.max(maxBinSize, bin.count);
});
});
// Dynamically create histograms for each target class
const histograms = targetClasses.map((target, index) => {
const startY = (index * height) / targetClasses.length;
const endY = ((index + 1) * height) / targetClasses.length;
return vl
.markBar()
.data(
bins[target].map((bin) => ({
...bin,
target // Add the target field to the bins' data
}))
)
.encode(
vl.y().value(startY), // Set the starting y-value based on the current index
vl.y2().value(endY), // Set the ending y-value based on the current index
vl.x().fieldQ("binStart"),
vl.x2().fieldQ("binEnd"),
vl
.opacity()
.fieldQ("count")
.scale({ domain: [0, maxBinSize], range: [0, 1] }),
// .legend({
// title: "Count",
// values: [
// roundToNearestBase(maxBinSize * 0.1, 5),
// roundToNearestBase(maxBinSize / 2, 5),
// roundToNearestBase(maxBinSize * 0.9, 5)
// ]
// }),
vl
.color()
.fieldN("target")
.scale({
domain: targetClasses.map((target) => target), // Map target indices to class names
range: targetClasses.map((target) => getColor(target))
})
.legend(null)
);
});
const zeroLine = vl
.markRule({ strokeDash: [5, 5] }) // Make a dashed line
.data([{ y: 0 }]) // At y=0
.encode(vl.y().fieldQ("y"));
return vl
.layer(...histograms, linePlot, zeroLine)
.height(height)
.width(width)
.title({
text: `${featureName} for ${className}, Score: ${score.toFixed(2)}`,
color: getColor(classIndex)
}) // Set the overall chart title
.render(); // Combine the charts using layer
}