mervenoyan's picture
commit files to HF hub
bd9ac5f
raw
history blame
18.4 kB
console.clear();
var ttSel = d3.select("body").selectAppend("div.tooltip.tooltip-hidden");
// For result tables
const columns = ["object", "n", "n correct", "accuracy"];
const rowHeight = 50;
const rowWidth = 100;
const buffer = 2;
const classifierBlobWidth = 50;
const classifierBlobHeight = 460;
function drawShapesWithData(classifier) {
var divHeight = classifier.class == "show-shapes" ? 250 : 490;
var c = d3.conventions({
sel: d3.select("." + classifier.class).html(""),
width: 1300,
height: divHeight,
layers: "ds",
});
function runClassifier() {
classifier.isClassified = true;
var duration = 3000;
classifierSel.classed("is-classified", true);
graphResultsGroup.classed("is-classified", true);
drawResults();
buttonSel.text("Reset");
var minX = d3.min(shapeParams, (d) => d.endX - 50);
var timer = d3.timer((ms) => {
if (!classifier.isClassified) {
timer.stop();
shapeSel.classed("is-classified", false);
return;
}
var t = d3.easeCubicInOut(ms / duration);
t = d3.clamp(0, t, 1);
shapeParams.forEach((d, i) => {
d.x = d.startX + (d.endX - d.startX) * t;
d.y = d.startY + (d.endY - d.startY) * t;
d.isClassified = d.x > minX;
});
shapeSel
.translate((d) => [d.x, d.y])
.classed("is-classified", (d) => d.isClassified);
if (t == 1) {
timer.stop();
}
});
}
function resetClassifier() {
shapeSel.translate((d) => [d.startX, d.startY]);
shapeSel.classed("is-classified", false);
classifier.isClassified = false;
shapeSel
.transition("position")
.duration(0)
.translate((d) => [d.startX, d.startY]);
classifierSel.classed("is-classified", false);
graphResultsGroup.classed("is-classified", false);
if (classifier.class != "show-shapes") {
classifierBlobSel.attr("opacity", 100);
}
drawResults();
buttonSel.text("Run Classifier");
}
// Add run/reset button
var buttonSel = d3
.select("." + classifier.class + "-button")
.html("")
.append("button#run")
.at({
type: "button",
class: "classifier-button",
})
.text("Run Classifier")
.on("click", () => {
// if already classified, reset
if (classifier.isClassified) {
// Resetting
resetClassifier();
} else {
runClassifier();
}
});
// Backgrounds for different classifications
var classifierSel = c.svg
.append("g")
.at({
class: "classifier",
})
.translate([465, 20]);
classifierSel
.append("path.classifier-bg-shaded")
.at({
d: classifierBgPathTop,
// fill: "#ccc",
// stroke: "#000",
})
.translate([-50, 0]);
classifierSel
.append("text.classifier-bg-text")
.at({
fill: "#000",
textAnchor: "middle",
dominantBaseline: "central",
class: "monospace",
})
.text("shaded")
.translate([160, 15]);
classifierSel
.append("path.classifier-bg-unshaded")
.at({
d: classifierBgPathBottom,
})
.translate([-50, 160]);
classifierSel
.append("text.classifier-bg-text")
.at({
fill: "#000",
textAnchor: "middle",
dominantBaseline: "central",
class: "monospace",
})
.text("unshaded")
.translate([160, 175]);
// Add the shapes themselves
var shapeSel = c.svg
.appendMany("path.shape", shapeParams)
.at({
d: (d) => d.path,
class: (d) => "gt-" + d.gt + " " + d.correctness,
})
.translate(function (d) {
if (classifier.class == "show-shapes") {
return [d.initialX + 35, d.initialY-20];
} else {
return [d.startX, d.startY];
}
})
.call(d3.attachTooltip)
.on("mouseover", (d) => {
ttSel.html("");
if (classifier.usingLabel != "none") {
ttSel
.append("div")
.html(
`<span class="left">labeled:</span> <span class="monospace right">${toPropertyString(
d[classifier.usingLabel],
classifier.isRounding
).slice(0, -1)}</span>`
);
}
var gtSel = ttSel
.append("div")
.html(
`<span class="left">ground truth:</span> <span class="monospace right">${d.gt}</span>`
);
if (classifier.isClassified) {
ttSel
.append("div.labeled-row")
.html(
`<span class="left">classified as:</span> <span class="monospace right">${d.label}</span>`
);
ttSel
.append("div.correct-row")
.classed("is-correct-tooltip", d.correctness == "correct")
.html(`<br><span>${d.correctness}ly classified</span> `);
}
ttSel.classed("tt-text", true);
});
// If we're just showing shapes, ignore everything else
if (classifier.class == "show-shapes") return;
// Add "classifier" line
var classifierBlobSel = c.svg
.append("g")
.at({
class: "classifier-blob",
strokeWidth: 0,
})
.translate([378, 20]);
classifierBlobSel
.append("line.classifier-blob")
.at({
class: "line",
x1: 27,
x2: 27,
y1: 0,
y2: 464,
stroke: "#000",
strokeWidth: 1,
})
.style("stroke-dasharray", "5, 5");
classifierBlobSel
.append("text.classifier-blob-text")
.at({
class: "classifier-blob-text monospace",
textAnchor: "middle",
dominantBaseline: "central",
})
.text("is_shaded classifier")
.attr("transform", "translate(30,480) rotate(0)");
if (classifier.class == "show-shapes") {
classifierBlobSel.classed("is-classified", true);
}
// Draw the results table with accuracies
// This will be hidden before classifier is run.
var graphResultsGroup = c.svg
.append("g")
.attr("class", "results")
.translate([-20, 19]);
function drawResults() {
// Write text summary
summarySel = d3
.select("." + classifier.class + "-summary")
.html(summaries[classifier.class])
.translate([0, 20]);
summarySel.classed("summary-text", true);
summarySel.classed("is-classified", classifier.isClassified);
if (!classifier.isClassified) {
c.layers[0].html("");
classifier.wasClassified = false;
return;
}
// Access results, which are calculated in shapes.js.
// If there are none, draw nothing.
results = allResults[classifier.class];
if (!results) return;
// Figure out which shapes should be highlighted on mouseover
// This depends on whether we're "rounding" edge case examples.
function isMatch(rowName, labelName, isRounding) {
// Not filtering at all
if (rowName == "shape") {
return true;
}
if (isRounding == true) {
// No "other" category
return labelName.includes(toOriginalString(rowName))
? true
: false;
} else {
// There is an "other" category, prefixed by "rt_"
if (labelName == toOriginalString(rowName)) {
return true;
} else if (
labelName.includes("rt_") &&
rowName == "other shapes"
) {
return true;
}
return false;
}
}
// Color the last row of each table
function getColor(d, i) {
if (i != 3) {
// not last index
return "#e6e6e6";
} else {
var scaleRowValue = d3
.scaleLinear()
.domain([0.3, 1.0])
.range([0, 1]);
return d3.interpolateRdYlGn(scaleRowValue(d));
}
}
// Adjust text color for visibility
function getTextColor(d, i) {
if (i != 3) {
// not last index
return "#000000";
} else {
var bgColor = getColor(d, i);
if (d < 0.3) {
// Alternative: use a brighter color?
// return d3.rgb(bgColor).brighter(-2);
return "#FFCCD8";
} else {
// Alternative: use a darker color?
// return d3.rgb(bgColor).darker(2);
return "#000000";
}
}
}
// Draw results table
var tableSel = c.layers[0]
.html("")
.raise()
.st({ width: 400 })
.append("div")
.translate([0, 10])
.append("table.results-table.monospace")
.st({ width: 400 });
var header = tableSel
.append("thead")
.append("tr")
.appendMany("th", columns)
.text((d) => d);
var rowSel = tableSel
.appendMany("tr", results)
.at({
class: "row monospace",
})
.on("mouseover", (row) => {
if (classifier.class == "default-classifier") {
return;
}
rowSel.classed("active", (d) => d == row);
shapeSel.classed("shape-row-unhighlighted", function (d) {
return !isMatch(
row.object,
d[classifier.usingLabel],
(isRounding = classifier.isRounding)
);
});
})
.on("mouseout", (row) => {
rowSel.classed("active", function (d) {
if (d == row) {
return false;
}
});
if (classifier.isClassified) {
shapeSel.classed("shape-row-unhighlighted", 0);
}
});
rowSel
.appendMany("td", (result) =>
columns.map((column) => result[column])
)
.text((d) => d)
.st({
backgroundColor: getColor,
color: getTextColor,
});
header.style("opacity", 0);
rowSel.style("opacity", 0);
// If the classifier has already been run before, draw results right away.
// Otherwise, wait for other animation to run before drawing results.
var initialDelay = classifier.wasClassified ? 0 : 2000;
classifier.wasClassified = true;
header
.transition()
.delay(initialDelay)
.duration(1000)
.style("opacity", 1);
rowSel
.transition()
.delay(function (d, i) {
return initialDelay + i * 200;
})
.duration(1000)
.style("opacity", 1);
}
// Draw the dropdowns for selecting different labels
function drawDropdown() {
if (!classifier.options) return;
["rounding", "category"].forEach(function (classifierType) {
if (!classifier.options[classifierType]) return;
var sel = d3
.select("#" + classifier.class + "-select-" + classifierType)
.html("");
sel.classed("dropdown", true);
sel.appendMany("option", classifier.options[classifierType])
.at({
value: function (d) {
return d.value;
},
})
.text((d) => d.label);
sel.on("change", function () {
if (classifierType == "rounding") {
classifier.isRounding = toBool(this.value);
} else {
classifier.usingLabel = this.value;
}
updateResults();
drawResults();
});
});
}
drawDropdown();
updateResults();
drawResults();
// For continuity, auto-run the second two classifiers
if (
classifier.class == "second-classifier" ||
classifier.class == "final-classifier"
) {
runClassifier();
}
}
// Draw the "Labels Tell Stories" section
function drawConclusion() {
function drawNewspapers() {
d3.select(".conclusion-newspapers").html(function () {
var imgPath =
"img/newspapers_" +
document.getElementById("conclusion-select-category").value;
return (
'<img src="' +
imgPath +
'.png" class="newspaper-image" alt="Newspapers with headlines about bias and fairness in shape data." width=400></img>'
);
});
}
function drawInterface() {
d3.select(".conclusion-interface").html(function () {
var imgPath =
"img/confusing_" +
document.getElementById("conclusion-select-category").value;
return (
'<center><img class="interface-image" width="638" height="268" src="' +
imgPath +
'.png" alt="A shape that is difficult to classify with several checkboxes, none of which describe the shape. Next to the interface is a text box with a single question mark in it." srcset="' +
imgPath +
'.svg"></img></center>'
);
});
}
function drawConclusionSummary() {
classifierSel = d3
.select(".conclusion-summary")
.html(summaries["conclusion"]);
classifierSel.classed("summary-text is-classified", true);
}
function drawDropdown() {
var sel = d3.select("#conclusion-select-category").html("");
sel.classed("dropdown", true);
sel.appendMany("option", conclusionOptions.category)
.at({
value: function (d) {
return d.value;
},
})
.text((d) => d.label);
// sel.attr('select', 'circles, triangles, and rectangles');
sel.on("change", function (d) {
makeConclusionUpdates();
});
}
function makeConclusionUpdates() {
updateResults();
drawNewspapers();
drawInterface();
drawConclusionSummary();
}
drawDropdown();
makeConclusionUpdates();
}
// Handle the parameters everywhere classifiers are drawn
var classifiers = [
{
// Just the initial display of shapes, not interactive
class: "show-shapes",
colorBy: (d) => d.correctness,
isClassified: false,
isRounding: false,
usingLabel: "none",
},
{
class: "default-classifier",
colorBy: (d) => d.correctness,
isClassified: false,
isRounding: false,
usingLabel: "none",
},
{
class: "second-classifier",
colorBy: (d) => d.correctness,
isClassified: false,
isRounding: true,
usingLabel: "shape_name",
options: {
rounding: [
{ label: "with their best guess", value: true },
{ label: 'as "other"', value: false },
],
},
},
{
class: "final-classifier",
colorBy: (d) => d.correctness,
isClassified: false,
isRounding: true,
usingLabel: "shape_name",
options: {
rounding: [
{ label: "with our best guess", value: true },
{ label: 'as "other"', value: false },
],
category: [
{
label: "circles, triangles, or rectangles",
value: "shape_name",
},
{ label: "pointy shapes or round shapes", value: "pointiness" },
{ label: "small shapes or big shapes", value: "size" },
{ label: "just shapes", value: "none" },
],
},
},
];
// "Labels Tell Stories" dropdown options
var conclusionOptions = {
category: [
{ label: "circles, triangles, and rectangles", value: "shape_name" },
{ label: "pointy shapes and round shapes", value: "pointiness" },
{ label: "small shapes and big shapes", value: "size" },
],
};
classifiers.forEach(drawShapesWithData);
drawConclusion();
// These images are loaded invisibly so they appear seamlessly on dropdown change
const preloadImages = [
"img/confusing_pointiness.png",
"img/confusing_pointiness.svg",
"img/confusing_shape_name.png",
"img/confusing_shape_name.svg",
"img/confusing_size.png",
"img/confusing_size.svg",
"img/interface_default.png",
"img/interface_default.svg",
"img/interface_shape_name_false.png",
"img/interface_shape_name_false.svg",
"img/interface_shape_name_true.png",
"img/interface_shape_name_true.svg",
"img/newspapers_pointiness.png",
"img/newspapers_pointiness.svg",
"img/newspapers_shape_name.png",
"img/newspapers_shape_name.svg",
"img/newspapers_size.png",
"img/newspapers_size.svg",
];
d3.select(".preload-dropdown-img")
.html("")
.appendMany("img", preloadImages)
.at({ src: (d) => d });