Published
Edited
Feb 23, 2022
8 stars
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
mutable cellWidth = 0
Insert cell
mutable cellHeight = 0
Insert cell
mutable horizontalAxisWidth = 0
Insert cell
mutable horizontalAxisHeight = 0
Insert cell
mutable verticalAxisWidth = 0
Insert cell
mutable verticalAxisHeight = 0
Insert cell
mutable imgSrc = ""
Insert cell
mutable axisXImgSrc = ""
Insert cell
mutable axisYImgSrc = ""
Insert cell
whiteRectangleImgSrc = FileAttachment('whiterectangle.png').url()
Insert cell
svgs = d3.select(visual)
Insert cell
svgGroup = {
let svgGroup = svgs
.selectAll(".draggableGroup")
.data([{ x: 0, y: 0 }])
.enter()
.append("g")
.attr("class", "draggableGroup" + " cleanOnInit")
.attr("id", "parentGroup")
.attr("draggable", true)
.on('touchstart', () => {
configuration.dragStartX = d3.event.touches[0].clientX;
configuration.dragStartY = d3.event.touches[0].clientY;
})
.on("touchmove", () => {
transformGroup(
bHorizontalWrappingConfig,
bVerticalWrappingConfig,
bDiagonalWrappingConfig,
d3.event.touches[0].clientX - configuration.dragStartX,
d3.event.touches[0].clientY - configuration.dragStartY
);

configuration.dragStartX = d3.event.x;
configuration.dragStartY = d3.event.y;
})
.call(
d3
.drag()
.on('start', () => {
configuration.dragStartX = d3.event.x;
configuration.dragStartY = d3.event.y;
})
.on("drag", () => {
transformGroup(
bHorizontalWrappingConfig,
bVerticalWrappingConfig,
bDiagonalWrappingConfig,
d3.event.x - configuration.dragStartX,
d3.event.y - configuration.dragStartY
);

configuration.dragStartX = d3.event.x;
configuration.dragStartY = d3.event.y;
})
.on("end", () => {
transformGroup(
bHorizontalWrappingConfig,
bVerticalWrappingConfig,
bDiagonalWrappingConfig,
d3.event.x - configuration.dragStartX,
d3.event.y - configuration.dragStartY
);

configuration.dragStartX = d3.event.x;
configuration.dragStartY = d3.event.y;
})
);
return svgGroup;
}
Insert cell
{
bHorizontalWrappingConfig;
bVerticalWrappingConfig;
bDiagonalWrappingConfig;

//update svgGroup to use correct bHorizontalWrappingConfig and bVerticalWrappingConfig
let svgGroup = svgs
.selectAll(".draggableGroup")
.data([{ x: 0, y: 0 }])
.attr("class", "draggableGroup" + " cleanOnInit")
.attr("id", "parentGroup")
.attr("draggable", true)
.call(
d3
.drag()
.on('start', () => {
configuration.dragStartX = d3.event.x;
configuration.dragStartY = d3.event.y;
})
.on("drag", () => {
transformGroup(
bHorizontalWrappingConfig,
bVerticalWrappingConfig,
bDiagonalWrappingConfig,
d3.event.x - configuration.dragStartX,
d3.event.y - configuration.dragStartY
);

configuration.dragStartX = d3.event.x;
configuration.dragStartY = d3.event.y;
})
.on("end", () => {
transformGroup(
bHorizontalWrappingConfig,
bVerticalWrappingConfig,
bDiagonalWrappingConfig,
d3.event.x - configuration.dragStartX,
d3.event.y - configuration.dragStartY
);

configuration.dragStartX = d3.event.x;
configuration.dragStartY = d3.event.y;
})
);
return svgGroup;
}
Insert cell
configuration = new Object({
canvasX: 0,
canvasY: 0,
originX: 0,
originY: 0,
dragStartX: 0,
dragStartY: 0,
width: 30000, //Number.MAX_SAFE_INTEGER,
height: 30000, //Number.MAX_SAFE_INTEGER
svgWidth: cellWidth * 3,
svgHeight: cellHeight * 3,
currentDraggingNode: {},
nonOverlappingDistance: 20,
hasBeenFixed: false
})
Insert cell
boundaryConfig = ({
oneThirdWidth: cellWidth,
twoThirdsWidth: cellWidth + cellWidth,
oneThirdHeight: cellHeight,
twoThirdsHeight: cellHeight + cellHeight
})
Insert cell
rectangle = {
let rectangle = svgGroup
.selectAll(".background")
.data([{ x: 0, y: 0 }])
.enter()
.append("rect")
.attr("x", configuration.originX)
.attr("y", configuration.originY)
.attr("rx", 0)
.attr("ry", 0)
.attr("fill-opacity", 0)
.attr("width", configuration.width)
.attr("height", configuration.height)
.attr("class", "background" + " cleanOnInit")
// .attr("class", "background")
.style("stroke", "black")
.lower();
return rectangle;
}
Insert cell
white_rectangle = {
let originX = 0;
let originY = 0;
if (
datasetname.indexOf("matrix") >= 0 ||
datasetname.indexOf("horizonchart") >= 0
) {
originX = cellWidth - verticalAxisWidth;
originY = cellHeight;
} else {
originX = cellWidth - verticalAxisWidth;
originY = cellHeight + cellHeight;
}
let white_rectangle_canvas = svgs
.append("foreignObject")
.attr("x", originX)
.attr("y", originY)
.attr("width", verticalAxisWidth)
.attr("height", horizontalAxisHeight)
.raise();
let canvas = white_rectangle_canvas
.node()
.appendChild(document.createElement('canvas'));
d3.select(canvas)
.attr("x", 0)
.attr("y", 0)
.style("display", "block")
.style("margin", 0)
.style("border", "0px none black")
.attr("width", verticalAxisWidth)
.attr("height", horizontalAxisHeight)
.raise();
// get drawing context of canvas
let ctx = canvas.getContext("2d");
draw(ctx, whiteRectangleImgSrc);
}
Insert cell
foreignObject = {
let foreignObject = [];

for (let i = 0; i < 9; i++) {
let originX = 0;
let originY = 0;
if (
datasetname.indexOf("matrix") >= 0 ||
datasetname.indexOf("horizonchart") >= 0
) {
originX = configuration.canvasX + shiftDistance[i].x;
originY =
configuration.canvasY + horizontalAxisHeight + shiftDistance[i].y;
} else {
originX = configuration.canvasX + shiftDistance[i].x;
originY = configuration.canvasY + shiftDistance[i].y;
}
foreignObject.push(
svgs
.append("foreignObject")
.attr("x", originX)
.attr("y", originY)
.attr("width", cellWidth)
.attr("height", cellHeight)
.lower()
);

// add embedded body to foreign object
// let foBody = foreignObject[foreignObject.length - 1]
// .append("xhtml:body")
// .style("margin", "0px")
// .style("padding", "0px")
// .style("background-color", "none")
// .style("width", cellWidth + "px")
// .style("height", cellHeight + "px")
// .style("border", "0px none lightgray")
// .lower();

// add embedded canvas to embedded body
// let canvas = foBody
// .append("canvas")
// .attr("x", 0)
// .attr("y", 0)
// .style("display", "block")
// .style("margin", 0)
// .style("border", "0px none black")
// .attr("width", cellWidth)
// .attr("height", cellHeight)
// .lower();

// get drawing context of canvas
//let ctx = canvas.node().getContext("2d");

let canvas = foreignObject[foreignObject.length - 1]
.node()
.appendChild(document.createElement('canvas'));

d3.select(canvas)
.attr("x", 0)
.attr("y", 0)
.style("display", "block")
.style("margin", 0)
.style("border", "0px none black")
.attr("width", cellWidth)
.attr("height", cellHeight)
.lower();

// get drawing context of canvas
let ctx = canvas.getContext("2d");
draw(ctx, imgSrc);
}
return foreignObject;
}
Insert cell
axisXObject = {
let axisXObject = [];

for (let i = 0; i < 3; i++) {
let originX = 0;
let originY = 0;
if (
datasetname.indexOf("matrix") >= 0 ||
datasetname.indexOf("horizonchart") >= 0
) {
originX = configuration.canvasX + axisXShiftDistance[i].x;
originY = cellHeight;
} else {
originX = configuration.canvasX + axisXShiftDistance[i].x;
originY = cellHeight + cellHeight;
}
axisXObject.push(
svgs
.append("foreignObject")
.attr("x", originX)
.attr("y", originY)
.attr("width", cellWidth)
.attr("height", horizontalAxisHeight)
.raise()
);

// add embedded body to foreign object
let foBody = axisXObject[axisXObject.length - 1]
.append("xhtml:body")
.style("margin", "0px")
.style("padding", "0px")
.style("background-color", "none")
.style("width", cellWidth + "px")
.style("height", horizontalAxisHeight + "px")
.style("border", "0px none lightgray");

// add embedded canvas to embedded body
let canvas = foBody
.append("canvas")
.attr("x", 0)
.attr("y", 0)
.style("display", "block")
.style("margin", 0)
.style("border", "0px none black")
.attr("width", cellWidth)
.attr("height", horizontalAxisHeight);

// get drawing context of canvas
let ctx = canvas.node().getContext("2d");
draw(ctx, axisXImgSrc);
}
return axisXObject;
}
Insert cell
axisYObject = {
let axisYObject = [];

for (let i = 0; i < 3; i++) {
let originX = 0;
let originY = 0;
if (
datasetname.indexOf("matrix") >= 0 ||
datasetname.indexOf("horizonchart") >= 0
) {
originX = cellWidth - verticalAxisWidth;
originY =
configuration.canvasY + horizontalAxisHeight + axisYShiftDistance[i].y;
} else {
originX = cellWidth - verticalAxisWidth;
originY = configuration.canvasY + axisYShiftDistance[i].y;
}
axisYObject.push(
svgs
.append("foreignObject")
.attr("x", originX)
.attr("y", originY)
.attr("width", verticalAxisWidth)
.attr("height", cellHeight)
.raise()
);

// add embedded body to foreign object
let foBody = axisYObject[axisYObject.length - 1]
.append("xhtml:body")
.style("margin", "0px")
.style("padding", "0px")
.style("background-color", "none")
.style("width", verticalAxisWidth + "px")
.style("height", cellHeight + "px")
.style("border", "0px none lightgray");

// add embedded canvas to embedded body
let canvas = foBody
.append("canvas")
.attr("x", 0)
.attr("y", 0)
.style("display", "block")
.style("margin", 0)
.style("border", "0px none black")
.attr("width", verticalAxisWidth)
.attr("height", cellHeight);

// get drawing context of canvas
let ctx = canvas.node().getContext("2d");
draw(ctx, axisYImgSrc);
}
return axisYObject;
}
Insert cell
//draw image
draw = (ctx, srcUrl) => {
let img = new Image();
img.onload = function() {
ctx.drawImage(img, 0, 0);
};
img.src = srcUrl;
}
Insert cell
transformGroup = (
bHorizontalWrappingConfig,
bVerticalWrappingConfig,
bDiagonalWrappingConfig,
offsetX,
offsetY
) => {
if (
bDiagonalWrappingConfig != null &&
bDiagonalWrappingConfig &&
datasetname.indexOf("matrix") >= 0
) {
// diagonal takes only the projection of the drag on the diagonal vector (1,1)
if (offsetX >= 0 && offsetY >= 0) {
configuration.canvasX += Math.max(Math.abs(offsetX), Math.abs(offsetY));
configuration.canvasY += Math.max(Math.abs(offsetX), Math.abs(offsetY));
} else if (offsetX < 0 && offsetY < 0) {
configuration.canvasX -= Math.max(Math.abs(offsetX), Math.abs(offsetY));
configuration.canvasY -= Math.max(Math.abs(offsetX), Math.abs(offsetY));
} else if (offsetX >= 0 && offsetY <= 0) {
if (Math.abs(offsetX) > Math.abs(offsetY)) {
configuration.canvasX += Math.max(Math.abs(offsetX), Math.abs(offsetY));
configuration.canvasY += Math.max(Math.abs(offsetX), Math.abs(offsetY));
} else if (Math.abs(offsetX) < Math.abs(offsetY)) {
configuration.canvasX -= Math.max(Math.abs(offsetX), Math.abs(offsetY));
configuration.canvasY -= Math.max(Math.abs(offsetX), Math.abs(offsetY));
}
} else if (offsetX < 0 && offsetY > 0) {
if (Math.abs(offsetX) > Math.abs(offsetY)) {
configuration.canvasX -= Math.max(Math.abs(offsetX), Math.abs(offsetY));
configuration.canvasY -= Math.max(Math.abs(offsetX), Math.abs(offsetY));
} else if (Math.abs(offsetX) < Math.abs(offsetY)) {
configuration.canvasX += Math.max(Math.abs(offsetX), Math.abs(offsetY));
configuration.canvasY += Math.max(Math.abs(offsetX), Math.abs(offsetY));
}
}
} else if (
bDiagonalWrappingConfig != null &&
bDiagonalWrappingConfig &&
datasetname.indexOf("rockscissorpaper") >= 0
) {
// antidiagonal, project on (-1,1)
if (offsetX >= 0 && offsetY <= 0) {
configuration.canvasX += Math.max(Math.abs(offsetX), Math.abs(offsetY));
configuration.canvasY -= Math.max(Math.abs(offsetX), Math.abs(offsetY));
} else if (offsetX < 0 && offsetY > 0) {
configuration.canvasX -= Math.max(Math.abs(offsetX), Math.abs(offsetY));
configuration.canvasY += Math.max(Math.abs(offsetX), Math.abs(offsetY));
} else if (offsetX > 0 && offsetY > 0) {
if (offsetX > offsetY) {
configuration.canvasX += Math.max(Math.abs(offsetX), Math.abs(offsetY));
configuration.canvasY -= Math.max(Math.abs(offsetX), Math.abs(offsetY));
} else {
configuration.canvasX -= Math.max(Math.abs(offsetX), Math.abs(offsetY));
configuration.canvasY += Math.max(Math.abs(offsetX), Math.abs(offsetY));
}
} else if (offsetX < 0 && offsetY < 0) {
if (Math.abs(offsetX) > Math.abs(offsetY)) {
configuration.canvasX -= Math.max(Math.abs(offsetX), Math.abs(offsetY));
configuration.canvasY += Math.max(Math.abs(offsetX), Math.abs(offsetY));
} else {
configuration.canvasX += Math.max(Math.abs(offsetX), Math.abs(offsetY));
configuration.canvasY -= Math.max(Math.abs(offsetX), Math.abs(offsetY));
}
}
} else {
if (bHorizontalWrappingConfig != null && bHorizontalWrappingConfig)
configuration.canvasX += offsetX;
if (bVerticalWrappingConfig != null && bVerticalWrappingConfig)
configuration.canvasY += offsetY;
}

if (configuration.canvasX > boundaryConfig.twoThirdsWidth) {
configuration.canvasX -= boundaryConfig.oneThirdWidth;
} else if (configuration.canvasX < boundaryConfig.oneThirdWidth) {
configuration.canvasX += boundaryConfig.oneThirdWidth;
}

if (configuration.canvasY > boundaryConfig.twoThirdsHeight) {
configuration.canvasY -= boundaryConfig.oneThirdHeight;
} else if (configuration.canvasY < boundaryConfig.oneThirdHeight) {
configuration.canvasY += boundaryConfig.oneThirdHeight;
}

for (var i = 0; i < 9; i++) {
foreignObject[i].attr("x", configuration.canvasX + shiftDistance[i].x);

if (
datasetname.indexOf("matrix") >= 0 ||
datasetname.indexOf("horizonchart") >= 0
)
foreignObject[i].attr(
"y",
configuration.canvasY + horizontalAxisHeight + shiftDistance[i].y
);
else foreignObject[i].attr("y", configuration.canvasY + shiftDistance[i].y);
}
svgs
.selectAll(".draggableGroup")
.selectAll(".whiteRectangle")
.raise();
for (var i = 0; i < 3; i++) {
axisXObject[i].attr("x", configuration.canvasX + axisXShiftDistance[i].x);
if (
datasetname.indexOf("matrix") >= 0 ||
datasetname.indexOf("horizonchart") >= 0
)
axisYObject[i].attr(
"y",
configuration.canvasY + horizontalAxisHeight + axisYShiftDistance[i].y
);
else
axisYObject[i].attr("y", configuration.canvasY + axisYShiftDistance[i].y);
}
}
Insert cell
shiftDistance = {
let width = cellWidth;
let height = cellHeight;
let shiftDistance = [
{ x: -width, y: -height },
{ x: 0, y: -height },
{ x: width, y: -height },
{ x: -width, y: 0 },
{ x: 0, y: 0 },
{ x: width, y: 0 },
{ x: -width, y: height },
{ x: 0, y: height },
{ x: width, y: height }
];
return shiftDistance;
}
Insert cell
axisXShiftDistance = {
let width = cellWidth;
let shiftDistance = [{ x: -width, y: 0 }, { x: 0, y: 0 }, { x: width, y: 0 }];
return shiftDistance;
}
Insert cell
axisYShiftDistance = {
let height = cellHeight;
let shiftDistance = [
{ x: 0, y: -height },
{ x: 0, y: 0 },
{ x: width, y: height }
];
return shiftDistance;
}
Insert cell
d3 = require("d3@5")
Insert cell
import { radio } from "@jashkenas/inputs"
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