Spaces:
Running
Running
console.clear(); | |
var shapeScale = 0.6; | |
var keyedData = { | |
pointiness_true: { | |
name: "pointiness_true", | |
isRounding: true, | |
categoryName: "pointiness", | |
categories: ["pointy", "round"], | |
textPlacements: {}, | |
}, | |
pointiness_false: { | |
name: "pointiness_false", | |
isRounding: false, | |
categoryName: "pointiness", | |
categories: ["pointy", "round", "other"], | |
textPlacements: {}, | |
}, | |
shape_name_true: { | |
name: "shape_name_true", | |
isRounding: true, | |
categoryName: "shape_name", | |
categories: ["circle", "triangle", "rect"], | |
textPlacements: {}, | |
}, | |
shape_name_false: { | |
name: "shape_name_false", | |
isRounding: false, | |
categoryName: "shape_name", | |
categories: ["circle", "triangle", "rect", "other"], | |
textPlacements: {}, | |
}, | |
size_true: { | |
name: "size_true", | |
isRounding: true, | |
categoryName: "size", | |
categories: ["small", "large"], | |
textPlacements: {}, | |
}, | |
size_false: { | |
name: "size_false", | |
isRounding: false, | |
categoryName: "size", | |
categories: ["small", "large", "other"], | |
textPlacements: {}, | |
}, | |
}; | |
var data = []; | |
for (var key in keyedData) { | |
data.push(keyedData[key]); | |
} | |
var state = { | |
selected: data[0], | |
selectedTopIndex: 0, | |
selectedBottomIndex: 0, | |
}; | |
function updateState( | |
category, | |
rounding, | |
topIndex = undefined, | |
bottomIndex = undefined | |
) { | |
var key = category + "_" + rounding; | |
state.selected = keyedData[key]; | |
state.selectedTopIndex = topIndex; | |
state.selectedBottomIndex = bottomIndex; | |
} | |
// Placements for the center labels | |
var textPlacements = {}; | |
var divHeight = 720; | |
var divWidth = 850; | |
var c = d3.conventions({ | |
sel: d3.select(".shape-explainer").html(""), | |
width: divWidth, | |
height: divHeight, | |
layers: "ds", | |
}); | |
var buttonHeight = 35; | |
var buttonWidth = 200; | |
var buttonBuffer = 15; | |
var topRightShift = 200; | |
var bottomRightShift = 270; | |
function setActiveButton() { | |
topExplainerButtonSel.classed( | |
"explainer-active-button", | |
(d, i) => i == state.selectedTopIndex | |
); | |
bottomExplainerButtonSel.classed( | |
"explainer-active-button", | |
(d, i) => i == state.selectedBottomIndex | |
); | |
} | |
// Preamble text | |
c.svg | |
.append("text.top-explainer-text") | |
.at({ | |
textAnchor: "left", | |
dominantBaseline: "top", | |
dy: ".33em", | |
}) | |
.translate([0, buttonHeight / 2]) | |
.text("All shapes are basically..."); | |
c.svg | |
.append("text.bottom-explainer-text") | |
.at({ | |
textAnchor: "left", | |
dominantBaseline: "top", | |
dy: ".33em", | |
}) | |
.translate([0, buttonHeight * 1.5 + buttonBuffer]) | |
.text("Everything else should be labeled..."); | |
// Buttons | |
var topExplainerButtonSel = c.svg | |
.appendMany("g.explainer-button", ["pointiness", "shape_name", "size"]) | |
.at({}) | |
.translate((d, i) => [topRightShift + i * (buttonWidth + buttonBuffer), 0]) | |
.on("click", function (d, i) { | |
updateState( | |
d, | |
state.selected.isRounding, | |
(topIndex = i), | |
(bottomIndex = state.selectedBottomIndex) | |
); | |
setActiveButton(); | |
moveShapes(); | |
}); | |
topExplainerButtonSel.append("rect").at({ | |
height: buttonHeight, | |
width: buttonWidth, | |
class: "explainer-rect", | |
}); | |
topExplainerButtonSel | |
.append("text") | |
.at({ | |
textAnchor: "middle", | |
dy: ".33em", | |
x: buttonWidth / 2, | |
y: buttonHeight / 2, | |
class: "dropdown", | |
}) | |
.text((d, i) => toShortValueStringDict[d]); | |
var bottomExplainerButtonSel = c.svg | |
.appendMany("g.explainer-button", ["true", "false"]) | |
.at({}) | |
.translate((d, i) => [ | |
bottomRightShift + i * (buttonWidth + buttonBuffer), | |
buttonHeight + buttonBuffer, | |
]) | |
.on("click", function (d, i) { | |
updateState( | |
state.selected.categoryName, | |
d, | |
(topIndex = state.selectedTopIndex), | |
(bottomIndex = i) | |
); | |
setActiveButton(); | |
moveShapes(); | |
}); | |
bottomExplainerButtonSel.append("rect").at({ | |
height: buttonHeight, | |
width: buttonWidth, | |
class: "explainer-rect", | |
}); | |
bottomExplainerButtonSel | |
.append("text") | |
.at({ | |
textAnchor: "middle", | |
dy: ".33em", | |
x: buttonWidth / 2, | |
y: buttonHeight / 2, | |
class: "dropdown", | |
}) | |
.text((d, i) => toDropdownValueRoundingStringDict[d]); | |
var horizontalHeight = divHeight * (5 / 8); | |
var horizontalBuffer = 50; | |
p = d3.line()([ | |
[horizontalBuffer, horizontalHeight], | |
[divWidth - horizontalBuffer, horizontalHeight], | |
]); | |
var horizontal = c.svg | |
.append("path") | |
.at({ | |
d: p, | |
stroke: "black", | |
strokeWidth: 1, | |
}) | |
.translate([0, 0]) | |
.style("stroke-dasharray", "5, 5"); | |
c.svg | |
.append("text.label-correct") | |
.at({ | |
x: -400, | |
y: 90, | |
}) | |
.text("correctly classified") | |
.attr("transform", "rotate(-90)"); | |
c.svg | |
.append("text.label-correct") | |
.at({ | |
x: -630, | |
y: 90, | |
}) | |
.text("incorrectly classified") | |
.attr("transform", "rotate(-90)"); | |
// Manually make some small adjustments to where particular shapes are placed | |
function getFineAdjustment(shape) { | |
if ( | |
shape.shape_name == "rt_rect" && | |
shape.correctness == "incorrect" && | |
shape.gt == "shaded" | |
) { | |
return 4; | |
} | |
if ( | |
shape.shape_name == "rect" && | |
shape.correctness == "incorrect" && | |
shape.gt == "unshaded" | |
) { | |
return -10; | |
} | |
if ( | |
shape.shape_name == "triangle" && | |
shape.correctness == "incorrect" && | |
shape.gt == "unshaded" | |
) { | |
return 0; | |
} | |
if ( | |
shape.shape_name == "rt_circle" && | |
shape.correctness == "incorrect" && | |
shape.size == "small" | |
) { | |
return -20; | |
} | |
if ( | |
shape.shape_name == "rt_triangle" && | |
shape.correctness == "incorrect" && | |
shape.size == "small" | |
) { | |
return -20; | |
} | |
return 0; | |
} | |
function getFinalCategory(labelName, isRounding) { | |
if (isRounding == true) { | |
return labelName.replace("rt_", ""); | |
} else { | |
if (labelName.includes("rt_")) { | |
return "other"; | |
} else { | |
return labelName; | |
} | |
} | |
} | |
var startingCorrectHeight = horizontalHeight - 50; | |
var startingIncorrectHeight = horizontalHeight + 50; | |
var maxHeight = 180; | |
var xRowAdjustment = 50; | |
var heightBuffer = 10; | |
function getPathHeight(inputPath) { | |
var placeholder = c.svg.append("path").at({ | |
d: scaleShapePath(inputPath, shapeScale), | |
}); | |
var height = placeholder.node().getBBox().height; | |
placeholder.remove(); | |
return height + heightBuffer; | |
} | |
// Figure out where to put the shapes for all possible placements | |
function generatePlacements() { | |
for (selectionCriteria of data) { | |
// starting X positions | |
var nCategories = selectionCriteria.categories.length; | |
var centerX = []; | |
for (var i = 0; i < nCategories; i++) { | |
var startingX = divWidth * ((i + 1) / (nCategories + 1)); | |
centerX.push(startingX); | |
// Track where each label should be placed using a dictionary in the data | |
selectionCriteria["textPlacements"][ | |
selectionCriteria.categories[i] | |
] = startingX; | |
} | |
// For keeping of track of how we place items as we go | |
var locationParams = {}; | |
for (categoryIdx in selectionCriteria.categories) { | |
var categoryName = selectionCriteria.categories[categoryIdx]; | |
locationParams[categoryName] = { | |
correctX: centerX[categoryIdx], | |
incorrectX: centerX[categoryIdx], | |
lastCorrectY: startingCorrectHeight, | |
lastIncorrectY: startingIncorrectHeight, | |
}; | |
} | |
for (shape of shapeParams) { | |
shapeCategory = getFinalCategory( | |
shape[selectionCriteria.categoryName], | |
selectionCriteria.isRounding | |
); | |
var shapeHeight = getPathHeight(shape.path); | |
var shapeX, | |
shapeY = 0; | |
if (shape.correctness == "correct") { | |
shapeY = locationParams[shapeCategory]["lastCorrectY"]; | |
shapeX = locationParams[shapeCategory]["correctX"]; | |
// Check if we've reached the maximum height | |
if ( | |
startingCorrectHeight - | |
locationParams[shapeCategory]["lastCorrectY"] >= | |
maxHeight | |
) { | |
// Reset height to baseline | |
locationParams[shapeCategory]["lastCorrectY"] = | |
startingCorrectHeight; | |
// Move next row over | |
locationParams[shapeCategory]["correctX"] = | |
locationParams[shapeCategory]["correctX"] + | |
xRowAdjustment; | |
} else { | |
locationParams[shapeCategory]["lastCorrectY"] += | |
-1 * shapeHeight; | |
} | |
} else { | |
shapeY = locationParams[shapeCategory]["lastIncorrectY"]; | |
shapeX = locationParams[shapeCategory]["incorrectX"]; | |
if ( | |
locationParams[shapeCategory]["lastIncorrectY"] - | |
startingIncorrectHeight >= | |
maxHeight | |
) { | |
// Reset height to baseline | |
locationParams[shapeCategory]["lastIncorrectY"] = | |
startingIncorrectHeight; | |
// Move next row over | |
locationParams[shapeCategory]["incorrectX"] = | |
locationParams[shapeCategory]["incorrectX"] + | |
xRowAdjustment; | |
} else { | |
locationParams[shapeCategory]["lastIncorrectY"] += | |
shapeHeight; | |
} | |
} | |
shapeY = shapeY + getFineAdjustment(shape); | |
shape[selectionCriteria.name + "_X"] = shapeX; | |
shape[selectionCriteria.name + "_Y"] = shapeY; | |
} | |
} | |
} | |
generatePlacements(); | |
function getLocation(shape) { | |
return [ | |
shape[state.selected.name + "_X"], | |
shape[state.selected.name + "_Y"], | |
]; | |
} | |
function scaleShapePath(shapePath, factor = 0.5) { | |
var newShapePath = ""; | |
for (var token of shapePath.split(" ")) { | |
if (parseInt(token)) { | |
newShapePath = newShapePath + parseInt(token) * factor; | |
} else { | |
newShapePath = newShapePath + token; | |
} | |
newShapePath = newShapePath + " "; | |
} | |
return newShapePath; | |
} | |
// Add the shapes | |
var explainerShapeSel = c.svg | |
.appendMany("path.shape", shapeParams) | |
.at({ | |
d: (d) => scaleShapePath(d.path, shapeScale), | |
class: (d) => "gt-" + d.gt + " " + d.correctness, | |
}) | |
.translate(function (d) { | |
return getLocation(d); | |
}); | |
explainerShapeSel.classed("is-classified", true); | |
function getColor(d) { | |
var scaleRowValue = d3.scaleLinear().domain([0.3, 1.0]).range([0, 1]); | |
return d3.interpolateRdYlGn(scaleRowValue(d)); | |
} | |
// Retrieve the results, for coloring the label boxes | |
function getResults() { | |
return calculateResults( | |
(property = state.selected.categoryName), | |
(useGuess = state.selected.isRounding) | |
); | |
} | |
function getCategoryAccuracy(results, category) { | |
for (var key of results) { | |
if (key.rawCategoryName == category) { | |
return key.accuracy; | |
} | |
} | |
} | |
// Rename "large" and "rect" | |
function toExplainerDisplayString(categoryName) { | |
if (categoryName == "large") { | |
return "big"; | |
} | |
if (categoryName == "rect") { | |
return "rectangle"; | |
} | |
return categoryName; | |
} | |
function getExplainerTextColor(d, i) { | |
console.log(d == "large"); | |
if (d == "large" && state.selected.isRounding == false) { | |
return "#ffccd8"; | |
} else { | |
return "#000000"; | |
} | |
} | |
function updateText() { | |
var explainerResults = getResults(); | |
d3.selectAll(".explainer-label-text").html(""); | |
d3.selectAll(".explainer-label-rect").remove(); | |
var rectHeight = 30; | |
var rectWidth = 80; | |
var textRect = c.svg | |
.appendMany("rect.column-text-rect", state.selected.categories) | |
.at({ | |
fill: (d) => getColor(getCategoryAccuracy(explainerResults, d)), | |
height: rectHeight, | |
width: rectWidth, | |
class: "explainer-label-rect", | |
}) | |
.translate((d) => [ | |
state.selected.textPlacements[d] - rectWidth / 2, | |
horizontalHeight - rectHeight / 2, | |
]); | |
var text = c.svg | |
.appendMany("text.column-text", state.selected.categories) | |
.at({ | |
textAnchor: "middle", | |
dominantBaseline: "central", | |
class: "explainer-label-text", | |
}) | |
.st({ | |
fill: getExplainerTextColor, | |
}) | |
.text((d) => toExplainerDisplayString(d)) | |
.translate((d) => [state.selected.textPlacements[d], horizontalHeight]); | |
} | |
function moveShapes() { | |
explainerShapeSel | |
.transition() | |
.duration(500) | |
.translate((d) => getLocation(d)); | |
updateText(); | |
} | |
setActiveButton(); | |
updateText(); |