Public
Edited
Nov 11, 2024
Importers
1 star
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// Plot.plot({
// width: width,
// height: 460,
// marks: [
// arealineY(aapl, {
// x: "Date",
// y: "Close",
// color: "steelblue",
// rough: {
// id: "firstDot",
// fillWeight: 1,
// hachureGap: 15,
// roughness: 10,
// bowing: 1,
// fillStyle: "hachure"
// }
// })
// ]
// })
Insert cell
Insert cell
Plot.plot({
x: { domain: [0, 1], grid: true },
marks: [
Plot.frame({
stroke: "blue",
rough: {
roughness: 20,
fillStyle: "zigzag"
}
})
]
})
Insert cell
// Plot.plot({
// width: width,
// inset: 10,
// color: { legend: true },
// marks: [
// // Plot.dot(penguins, {
// // x: "flipper_length_mm",
// // y: "culmen_length_mm",
// // stroke: "species",
// // fill: "island",
// // tip: true,
// // r: 8,
// // rough: {
// // roughness: 1,
// // bowing: 10,
// // fillStyle: "hachure"
// // }
// // }),
// arealineY(aapl, {
// x: "Date",
// y: "Close",
// color: "steelblue",
// rough: {
// id: "firstDot",
// fillWeight: 1,
// hachureGap: 15,
// roughness: 10,
// bowing: 1,
// fillStyle: "hachure"
// }
// }),
// Plot.frame({
// rough: {
// fillStyle: "hachure",
// roughness: 2,
// stroke: "black",
// fill: "none"
// }
// })
// ]
// })
Insert cell
// import {roughPlugin} from "@tututwo/rough-js-plugin-for-observable-plot"
Insert cell
function roughPlugin(Plot) {
function wrapPlotFunction(funcName) {
const originalFunc = Plot[funcName];
const specialMarks = new Set(["frame", "sphere", "graticule"]);
Plot[funcName] = (...args) => {
let data, options;

if (specialMarks.has(funcName)) {
// Marks that only take options
options = args[0] || {};
console.log("Special mark detected:", funcName, options);
} else if (args.length === 1 && !isIterable(args[0])) {
[options] = args;
data = null;
} else {
[data, options = {}] = args;
}

const { rough, ...restOptions } = options || {};

// Call the original function appropriately
const mark = specialMarks.has(funcName)
? originalFunc(restOptions)
: originalFunc(data, restOptions);

if (mark && typeof mark === "object") {
if (rough) {
const { id, ...roughOptions } = rough;
mark.rough = roughOptions;
mark.ariaLabel =
id ||
`rough-${funcName}-${Math.random().toString(36).substr(2, 9)}`;
}
}
return mark;
};
}
plotFunctions.forEach(wrapPlotFunction);

const originalPlot = Plot.plot;

Plot.plot = (options) => {
const svg = originalPlot(options);

function applyRoughToMark(mark) {
if (mark && mark.rough) {
const groupElements = svg.querySelectorAll(
`g[aria-label="${mark.ariaLabel}"], [aria-label="${mark.ariaLabel}"]`
);
console.log("Found elements:", groupElements);
if (groupElements.length > 0) {
applyRoughStyling(groupElements, mark.rough);
}
}
}

function processMarks(marks) {
marks.forEach((mark) => {
if (Array.isArray(mark)) {
// This is a custom mark (composite mark)
processMarks(mark);
} else if (typeof mark === "object" && mark !== null) {
// This is a normal mark
applyRoughToMark(mark);
}
});
}
// recursive processMarks function
processMarks(options.marks);
applyRoughToMark;

return svg;
};
return Plot;
}
Insert cell
// Plot.plot = (options) => {
// const svg = originalPlot(options);

// function applyRoughToMark(mark, parentRough) {
// if (Array.isArray(mark)) {
// // This is a composite mark (like from Plot.marks())
// mark.forEach(subMark => applyRoughToMark(subMark, parentRough));
// } else if (mark && (mark.rough || mark.isGrid || parentRough)) {
// const roughOptions = mark.rough || parentRough || (mark.isGrid ? { roughness: 0.5, bowing: 1 } : null);
// if (roughOptions) {
// const groupElements = svg.querySelectorAll(`g[aria-label="${mark.ariaLabel}"]`);
// if (groupElements.length > 0) {
// applyRoughStyling(groupElements, roughOptions);
// }
// }
// }
// }

// if (Array.isArray(options.marks)) {
// options.marks.forEach(mark => applyRoughToMark(mark));
// } else if (typeof options.marks === 'object') {
// Object.values(options.marks).forEach(mark => applyRoughToMark(mark));
// }

// return svg;
// };
Insert cell
function applyRoughStyling(gContainer, roughOptions) {
const roughSvg = roughJS.svg(gContainer);

gContainer.forEach((svg) => {
svg
.querySelectorAll(
"rect:not(clipPath *), circle:not(clipPath *), path:not(clipPath *), line:not(clipPath *)"
)
.forEach((element) => {
let roughElement;

// Extract fill and stroke from the original element
const fill =
svg.getAttribute("fill") ||
element.getAttribute("fill") ||
"transparent";
const stroke =
svg.getAttribute("stroke") ||
element.getAttribute("stroke") ||
"black";
const clipPath = element.getAttribute("clip-path") || "";

// Merge fill and stroke with roughOptions
const mergedOptions = {
...roughOptions,
fill: roughOptions.fill || fill,
stroke: roughOptions.stroke || stroke
};

switch (element.tagName.toLowerCase()) {
case "rect":
const x = parseFloat(element.getAttribute("x") || 0);
const y = parseFloat(element.getAttribute("y") || 0);
const width = parseFloat(element.getAttribute("width"));
const height = parseFloat(element.getAttribute("height"));
roughElement = roughSvg.rectangle(
x,
y,
width,
height,
mergedOptions
);
break;
case "circle":
const cx = parseFloat(element.getAttribute("cx"));
const cy = parseFloat(element.getAttribute("cy"));
const r = parseFloat(element.getAttribute("r"));
roughElement = roughSvg.circle(cx, cy, r * 2, mergedOptions);
break;
case "path":
roughElement = roughSvg.path(element.getAttribute("d"), {
...mergedOptions
});
break;
case "line":
const x1 = parseFloat(element.getAttribute("x1"));
const y1 = parseFloat(element.getAttribute("y1"));
const x2 = parseFloat(element.getAttribute("x2"));
const y2 = parseFloat(element.getAttribute("y2"));
roughElement = roughSvg.line(x1, y1, x2, y2, mergedOptions);
break;
}

if (roughElement) {
// Create a group to hold the rough element
// const group = document.createElementNS(
// "http://www.w3.org/2000/svg",
// "g"
// );
// element.parentNode.replaceChild(group, element);
// Apply clip-path to the group if it exists
if (clipPath) {
// group.setAttribute("clip-path", clipPath);
}
roughElement.setAttribute(
"transform",
element.getAttribute("transform") ?? ""
);
roughElement.setAttribute("class", "rough-elements-group");
// Replace the original element with the group

element.parentNode.insertBefore(roughElement, element);
// element.style.display = "none";
element.parentNode.removeChild(element);
}
});
});
}
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
// globalOption = {
// const originalPlot = Plot.plot;
// Plot.plot = (options = {}) => {
// const { rough: globalRough, ...restOptions } = options;
// if (globalRough) {
// restOptions.marks = restOptions.marks.map(mark => {
// if (typeof mark === 'function' && mark.rough === undefined) {
// return mark({ rough: globalRough });
// }
// return mark;
// });
// }
// return originalPlot(restOptions);
// };
// }
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