Public
Edited
Nov 11
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

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more