I am creating svg path and scaling it to a specific dimension. Let's suppose 100x100
. I am using d3 to shape the data as per my req.
However, I was wondering, how can I achieve the exact same output as pathStr
with pathfit. This is what I tried below.
function degreeToRadian(d) {
return (d * Math.PI) / 180;
};
function fnData() {
const data = d3.range(0, 11, 1).map((d) => {
const angleRadian = degreeToRadian(d);
return {
index: d,
angleDeg: d,
angleRadian: angleRadian,
x: Math.cos(angleRadian),
y: Math.sin(angleRadian)
}
});
return data;
};
const width = 1280;
const height = 720;
const data = fnData();
//------------------------WITH D3------------------------//
const scaleX = d3
.scaleLinear()
.range([0, 100])
.domain(d3.extent(data, (d) => d.x));
const scaleY = d3
.scaleLinear()
.range([100, 0])
.domain(d3.extent(data, (d) => d.y));
const pathStr = d3
.line()
.x((d) => scaleX(d.x))
.y((d) => scaleY(d.y))(data);
const pathStr2 = d3.line().x(d=>d.x).y(d=>d.y)(data);
//------------------------WITH pathfit------------------------//
const base = {
viewBox: `0 0 ${width} ${height}`,
preserveAspectRatio: "xMidYMid meet" // the default
};
const pathFitter = new Pathfit(base, undefined, pathStr2).scale_with_aspect_ratio(100, 100);
console.log({d3:pathStr, pathfit:pathFitter});
<script type="text/javascript" src="https://d3js.org/d3.v7.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/pathfit@1.0.4/pathfit.js"></script>
If you want to use the extent of your data as the box that should be expanded into the 100*100 dimensions, you have to set these data in the viewBox
. d3
can provide you with the coordinates of the four sides:
const [x1, x2] = d3.extent(data, (d) => d.x);
const [y1, y2] = d3.extent(data, (d) => d.y);
viewBox: `${x1} ${y1} ${x2 - x1} ${y2 - y1}`
If you want to not preserve the aspect ratio, but stretch the data to the maximum dimensions in both directions, you have to set
preserveAspectRatio: "none"
If you want to flip the data upside-down, you have to provide a pretransform value. This is certainly a bit more awkward as with d3
. Since SVG syntax is used, you can not specify a center of transformation directly, but need to find the correct formula for the transform matrix
yourself. Fortunately, an upside-down flip that leaves the bounds of of your data exactly in place is relatively easy:
`matrix(1 0 0 -1 0 ${y1 + y2})`
Unfortunately, the result will still look different from what d3
delivers, because the non-scaled path string from d3 fed to pathfit
has a too small number of digits after the decimal separator. This leads to rounding errors that appear huge in the end result. You will need to increase them from the default:
const pathStr2 = d3.line().digits(6).x(d=>d.x).y(d=>d.y)(data);
function degreeToRadian(d) {
return (d * Math.PI) / 180;
};
function fnData() {
const data = d3.range(0, 11, 1).map((d) => {
const angleRadian = degreeToRadian(d);
return {
index: d,
angleDeg: d,
angleRadian: angleRadian,
x: Math.cos(angleRadian),
y: Math.sin(angleRadian)
}
});
return data;
};
const width = 1280;
const height = 720;
const data = fnData();
//------------------------WITH D3------------------------//
const scaleX = d3
.scaleLinear()
.range([0, 100])
.domain(d3.extent(data, (d) => d.x));
const scaleY = d3
.scaleLinear()
.range([100, 0])
.domain(d3.extent(data, (d) => d.y));
const pathStr = d3
.line()
.x((d) => scaleX(d.x))
.y((d) => scaleY(d.y))(data);
const pathStr2 = d3.line().digits(5).x(d=>d.x).y(d=>d.y)(data);
//------------------------WITH pathfit------------------------//
const [x1, x2] = d3.extent(data, (d) => d.x);
const [y1, y2] = d3.extent(data, (d) => d.y);
const base = {
viewBox: `${x1} ${y1} ${x2 - x1} ${y2 - y1}`,
preserveAspectRatio: "none" // do stretch the result
};
const pathFitter = new Pathfit(
base,
undefined,
pathStr2,
`matrix(1 0 0 -1 0 ${y1 + y2})`,
{ precision: 4 }
).scale_with_aspect_ratio(100, 100);
console.log({d3:pathStr, pathfit:pathFitter});
<script type="text/javascript" src="https://d3js.org/d3.v7.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/pathfit@1.0.4/pathfit.js"></script>