Last active
August 31, 2023 18:40
-
-
Save abellaismail7/ad00634893450845433d68eeb8f10931 to your computer and use it in GitHub Desktop.
rectdonut.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const D3colors = [ | |
"#88d9cc", | |
"#edbd69", | |
"#f4e884", | |
"#f69787", | |
"#edd0b7", | |
"#c6d", | |
"#f4f", | |
]; | |
function createRect(svg, it, klass = "scalable rect", props) { | |
var color = it.color ? it.color : "black"; | |
svg | |
.append("g") | |
.attr("class", klass) | |
.attr("transform", it.transform) | |
.append("rect") | |
.attr("x", props.h / 2 + it.xstart * props.rectw) | |
.attr("y", it.y) | |
.attr("width", (it.xend - it.xstart) * props.rectw) | |
.attr("height", props.outerRadius - props.innerRadius) | |
.attr("fill", color); | |
} | |
function createArc(svg, it, klass = "scalable", props) { | |
var color = it.color ? it.color : "black"; | |
const cl = 2 * Math.PI; | |
const arc = d3 | |
.arc() | |
.innerRadius(props.innerRadius) | |
.outerRadius(props.outerRadius) | |
.startAngle(it.start * cl) | |
.endAngle(it.end * cl); | |
svg | |
.append("g") | |
.attr("transform", it.transform) | |
.append("path") | |
.attr("d", arc) | |
.attr("class", klass) | |
.attr("fill", color) | |
.append("arc"); | |
} | |
function createRectArc(svg, it, props) { | |
const g = svg.append("g").attr("class", "scalable tr").attr("fill", it.color); | |
it.shapes.forEach((it) => { | |
if (it.type === "rect") { | |
createRect(g, it, "", props); | |
return; | |
} | |
createArc(g, it, "", props); | |
}); | |
} | |
function rectData(props, val, index, offset, mode) { | |
if (mode === "rect2") { | |
console.log(offset, val); | |
return { | |
type: "rect", | |
xstart: 1 - (offset + val) / props.rectw, | |
xend: 1 - offset / props.rectw, | |
y: props.h - (props.outerRadius - props.innerRadius), | |
color: D3colors[index % D3colors.length], | |
transform: `translate(0, 0)`, | |
}; | |
} | |
return { | |
type: "rect", | |
xstart: offset / props.rectw, | |
xend: (offset + val) / props.rectw, | |
y: 0, | |
color: D3colors[index % D3colors.length], | |
transform: `translate(0, 0)`, | |
}; | |
} | |
function arcData(props, val, index, offset, mode) { | |
const arcLen = Math.PI * props.h; | |
const is2 = mode === "arc2" ? 0.5 : 0; | |
return { | |
type: "arc", | |
start: offset / arcLen + is2, | |
end: (offset + val) / arcLen + is2, | |
color: D3colors[index % D3colors.length], | |
transform: | |
mode === "arc2" | |
? `translate(${props.h / 2}, ${props.h / 2})` | |
: `translate(${props.w - props.h / 2}, ${props.h / 2})`, | |
}; | |
} | |
function getShape(offset, props) { | |
if (offset < props.rectw) { | |
return "rect"; | |
} | |
if (offset < props.rectw + Math.PI * (props.h / 2)) return "arc"; | |
if (offset < props.rectw * 2 + Math.PI * (props.h / 2)) return "rect2"; | |
return "arc2"; | |
} | |
function getNextData(props, val, index, offset) { | |
const bounds = { | |
rect: 0, | |
arc: props.rectw, | |
rect2: props.rectw + Math.PI * (props.h / 2), | |
arc2: props.rectw * 2 + Math.PI * (props.h / 2), | |
}; | |
let data; | |
let rest = 0; | |
const arcLen = Math.PI * (props.h / 2); | |
const mode = getShape(offset, props); | |
const ctxOffset = offset - bounds[mode]; | |
if (mode === "rect" || mode === "rect2") { | |
rest = ctxOffset + val - props.rectw; | |
data = rectData( | |
props, | |
rest > 0 ? props.rectw - ctxOffset : val, | |
index, | |
ctxOffset, | |
mode | |
); | |
} else if (mode === "arc" || mode === "arc2") { | |
rest = ctxOffset + val - arcLen; | |
data = arcData( | |
props, | |
rest > 0 ? arcLen - ctxOffset : val, | |
index, | |
ctxOffset, | |
mode | |
); | |
} | |
return [rest, data]; | |
} | |
function getData(props, val, index, offset) { | |
const bounds = { | |
rect: props.rectw, | |
arc: props.rectw + Math.PI * (props.h / 2), | |
rect2: props.rectw * 2 + Math.PI * (props.h / 2), | |
arc2: props.total, | |
}; | |
const begins = { | |
rect: 0, | |
arc: props.rectw, | |
rect2: props.rectw + Math.PI * (props.h / 2), | |
arc2: props.rectw * 2 + Math.PI * (props.h / 2), | |
}; | |
const type = getShape(offset, props); | |
if (offset + val > bounds[type]) { | |
const res = { | |
type: "rectarc", | |
color: D3colors[index % D3colors.length], | |
shapes: [], | |
}; | |
do { | |
const [rest, data] = getNextData(props, val, index, offset); | |
offset += val - rest; | |
val = rest; | |
res.shapes.push(data); | |
if (rest <= 0.01) break; | |
} while (true); | |
return res; | |
} | |
if (type === "rect" || type === "rect2") | |
return rectData(props, val, index, offset - begins[type], type); | |
if (type === "arc" || type === "arc2") | |
return arcData(props, val, index, offset - begins[type], type); | |
throw new Error("unknown type"); | |
} | |
function couponToData(coupons, props) { | |
const ctotal = coupons.reduce((acc, it) => acc + it.longeur, 0); | |
const scale = props.total / ctotal; | |
let offset = 0; | |
const data = []; | |
for (const [index, it] of coupons.entries()) { | |
data.push(getData(props, it.longeur * scale, index, offset)); | |
offset += it.longeur * scale; | |
} | |
return data; | |
} | |
const setup = (props, parent, data) => { | |
const svg = d3 | |
.select(parent) | |
.append("svg") | |
.attr("viewBox", "0 0 " + props.w + " " + props.h); | |
data.forEach((it) => { | |
if (it.type === "rect") { | |
createRect(svg, it, "", props); | |
} else if (it.type === "arc") { | |
createArc(svg, it, "", props); | |
} else if (it.type === "rectarc") { | |
createRectArc(svg, it, props); | |
} | |
}); | |
}; | |
window.FT_setup = setup; | |
window.FT_couponToData = couponToData; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment