Last active
September 2, 2021 15:39
-
-
Save Chrisa0418/da7190a12b6357c904828b170ae11129 to your computer and use it in GitHub Desktop.
Data vis - Chart (D3.js and React)
This file contains hidden or 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
export default function define(runtime, observer) { | |
const main = runtime.module(); | |
main.variable(observer()).define(["md"], function(md){return( | |
md`# Stock Chart` | |
)}); | |
main.variable(observer("chart")).define("chart", ["d3","width","height","margin","rectColor","rectHeight","chartData","x","y","lineColor","yLine","line","hoverScale","dateFormat","priceFormat","apyFormat"], function(d3,width,height,margin,rectColor,rectHeight,chartData,x,y,lineColor,yLine,line,hoverScale,dateFormat,priceFormat,apyFormat) | |
{ | |
let svg = d3 | |
.create("svg") | |
.attr("viewBox", [0, 0, width, height]) | |
.style("background", "#1B1A40"); | |
svg.append("style").text(` | |
.tranche--active { | |
text-decoration: underline | |
} | |
`); | |
var apyToggle = true; | |
var t = d3.transition().duration(1000); | |
const dataProps = [ | |
{ line: "netAApy", bar: "trancheAValueUSD", sliceLine: "sliceAApy" }, | |
{ line: "netBApy", bar: "trancheBValueUSD", sliceLine: "sliceBApy" } | |
]; | |
let selectedData = dataProps[0]; | |
let trancheToggle = true; | |
let hoveredIndex = -1; | |
let hoverGroup = svg | |
.append("g") | |
.attr("transform", `translate(0,${margin.bottom})`); | |
let hoverLine = hoverGroup | |
.append("line") | |
.attr("x1", 0) | |
.attr("x2", 0) | |
.attr("y1", 0) | |
.attr("y2", 0) | |
.attr("stroke-dasharray", "4,4") | |
.attr("stroke", "white") | |
.attr("stroke-width", 2) | |
.attr("class", "hover"); | |
let dateText = svg | |
.append("text") | |
.attr("opacity", 0) | |
.attr("fill", "white") | |
.attr("font-size", 14) | |
.attr("text-anchor", "middle") | |
.text("gigi"); | |
let rects = svg | |
.append("g") | |
.attr("fill", rectColor) | |
.attr("transform", `translate(0,${height - rectHeight - margin.bottom})`); | |
function updateRects() { | |
rects | |
.selectAll("rect") | |
.data(chartData[selectedData.bar]) | |
.join("rect") | |
.attr("x", (d, i) => x(i)) | |
.attr("class", (d, i) => `rect-${i} bar`) | |
.attr("width", x.bandwidth()) | |
.transition() | |
.duration(2000) | |
.attr("y", (d) => y(d)) | |
.attr("height", (d) => y(0) - y(d)); | |
} | |
let hoverText = rects | |
.append("text") | |
.attr("opacity", 0) | |
.attr("class", "hover") | |
.attr("fill", "white") | |
.attr("text-anchor", "middle") | |
.text(""); | |
let subtitleHoverText = rects | |
.append("text") | |
.attr("opacity", 0) | |
.attr("class", "hover") | |
.attr("fill", "white") | |
.attr("font-size", 10) | |
.attr("text-anchor", "middle") | |
.text("TRANCHE VALUE"); | |
let lineGroup = svg | |
.append("g") | |
.attr("class", "line-group") | |
.attr("transform", `translate(0,${rectHeight - 50})`); | |
var netALine = lineGroup | |
.append("path") | |
.attr("stroke", lineColor) | |
.attr("fill", "none") | |
.attr("stroke-width", 3) | |
.attr("opacity", 0.8); | |
var netBLine = lineGroup | |
.append("path") | |
.attr("stroke", lineColor) | |
.attr("fill", "none") | |
.attr("stroke-width", 3) | |
.attr("opacity", 0.2); | |
function updateLines() { | |
var aData = apyToggle | |
? chartData[dataProps[0]["line"]] | |
: chartData[dataProps[0]["sliceLine"]]; | |
var bData = apyToggle | |
? chartData[dataProps[1]["line"]] | |
: chartData[dataProps[1]["sliceLine"]]; | |
yLine.domain([0, d3.max([d3.max(aData), d3.max(bData)])]); | |
netALine.attr("d", line(aData)); | |
netBLine.attr("d", line(bData)); | |
var aLabel = "Tranche A"; | |
var bLabel = "Tranche B"; | |
netAApyLabel.text(aLabel); | |
netBApyLabel.text(bLabel); | |
} | |
var title = svg | |
.append("text") | |
.attr("fill", "white") | |
.attr("text-anchor", "start") | |
.attr("x", margin.left) | |
.attr("y", 40) | |
.attr("font-size", 22) | |
.attr("font-weight", 700) | |
.text("Tranche Performance"); | |
const toggleWidth = 200; | |
var toggleHeight = 30; | |
var toggleGroup = svg | |
.append("g") | |
.attr("transform", `translate(250,${40 - toggleHeight + 5})`); | |
let toggleRect = toggleGroup | |
.append("rect") | |
.attr("x", margin.left) | |
.attr("y", 0) | |
.attr("width", toggleWidth) | |
.attr("height", toggleHeight) | |
.attr("fill", "#26254A") | |
.attr("rx", toggleHeight / 2) | |
.attr("ry", toggleHeight / 2); | |
let activeRect = toggleGroup | |
.append("rect") | |
.attr("x", margin.left) | |
.attr("y", 0) | |
.attr("width", toggleWidth / 2) | |
.attr("height", toggleHeight) | |
.attr("fill", "#3B3765") | |
.attr("rx", toggleHeight / 2) | |
.attr("ry", toggleHeight / 2); | |
let netApyRectLabel = toggleGroup | |
.append("text") | |
.attr("x", margin.left + toggleWidth / 4) | |
.attr("y", toggleHeight / 2 + 5) | |
.attr("fill", "white") | |
.attr("text-anchor", "middle") | |
.attr("font-size", 12) | |
.attr("font-weight", 700) | |
.text("NET APY"); | |
let sliceApyRectLabel = toggleGroup | |
.append("text") | |
.attr("x", margin.left + toggleWidth / 4 + toggleWidth / 2) | |
.attr("y", toggleHeight / 2 + 5) | |
.attr("fill", "white") | |
.attr("opacity", 0.5) | |
.attr("text-anchor", "middle") | |
.attr("font-size", 12) | |
.attr("font-weight", 700) | |
.text("SLICE APY"); | |
let hoverCircle = lineGroup | |
.append("circle") | |
.attr("r", 10) | |
.attr("opacity", 0) | |
.attr("stroke", "white") | |
.attr("stroke-width", 4) | |
.attr("fill", lineColor); | |
let netAApyLabel = lineGroup | |
.append("text") | |
.attr("fill", "white") | |
.attr("font-size", 12) | |
.attr("font-weight", 700) | |
.attr("x", width - margin.right + 10) | |
.attr("y", yLine(chartData.netAApy[chartData.netAApy.length - 1]) + 4) | |
.style("cursor", "pointer") | |
.style("opacity", trancheToggle ? 0.8 : 0.2) | |
.text("Tranche A") | |
.on("mousedown", function (e) { | |
trancheToggle = true; | |
selectedData = dataProps[0]; | |
netALine.transition().duration(1000).style("opacity", 0.8); | |
netBLine.transition().duration(1000).style("opacity", 0.2); | |
netAApyLabel.transition().duration(1000).style("opacity", 1); | |
netBApyLabel.transition().duration(1000).style("opacity", 0.2); | |
updateRects(); | |
}); | |
let netBApyLabel = lineGroup | |
.append("text") | |
.attr("fill", "white") | |
.attr("font-size", 12) | |
.attr("font-weight", 700) | |
.attr("x", width - margin.right + 10) | |
.attr("y", yLine(chartData.netBApy[chartData.netBApy.length - 1]) + 4) | |
.style("cursor", "pointer") | |
.style("opacity", trancheToggle ? 0.2 : 0.8) | |
.text("Tranche B") | |
.on("mousedown", function (e) { | |
trancheToggle = false; | |
selectedData = dataProps[1]; | |
netBLine.transition().duration(1000).style("opacity", 0.8); | |
netALine.transition().duration(1000).style("opacity", 0.2); | |
netAApyLabel.transition().duration(1000).style("opacity", 0.2); | |
netBApyLabel.transition().duration(1000).style("opacity", 1); | |
updateRects(); | |
}); | |
let toggleOverlay = toggleGroup | |
.append("rect") | |
.attr("x", margin.left) | |
.attr("y", 0) | |
.attr("width", toggleWidth) | |
.attr("height", toggleHeight) | |
.attr("opacity", 0) | |
.attr("rx", toggleHeight / 2) | |
.attr("ry", toggleHeight / 2) | |
.style("cursor", "pointer") | |
.on("mousedown", function (e) { | |
var offset = margin.left + toggleWidth / 2; | |
if (!apyToggle) offset = margin.left; | |
activeRect.transition().attr("x", offset); | |
var netOpacity = apyToggle ? 0.5 : 1; | |
var sliceOpacity = apyToggle ? 1 : 0.5; | |
netApyRectLabel.transition().attr("opacity", netOpacity); | |
sliceApyRectLabel.transition().attr("opacity", sliceOpacity); | |
apyToggle = !apyToggle; | |
updateLines(); | |
}); | |
let lineValueText = lineGroup | |
.append("text") | |
.attr("fill", "white") | |
.attr("font-size", 12) | |
.attr("text-anchor", "middle") | |
.attr("opacity", 0); | |
let lineValueTextSubtitle = lineGroup | |
.append("text") | |
.attr("fill", "white") | |
.attr("font-size", 10) | |
.attr("text-anchor", "middle") | |
.attr("opacity", 0); | |
let hoverRect = svg | |
.append("rect") | |
.attr("opacity", 0) | |
.attr("fill", "white") | |
.attr("x", margin.left) | |
.attr("y", margin.top) | |
.attr("width", width - margin.right - margin.left) | |
.attr("height", height - margin.bottom) | |
.on("mousemove", function (event, d) { | |
var index = Math.floor(hoverScale.invert(event.clientX)); | |
updateHover(index); | |
}) | |
.on("mouseout", function (event, d) { | |
clearHover(); | |
}); | |
updateRects(); | |
updateLines(); | |
function updateHover(index) { | |
if (index == hoveredIndex) return; | |
else hoveredIndex = index; | |
d3.selectAll(".bar").attr("fill", rectColor); | |
var activeProp = apyToggle ? "line" : "sliceLine"; | |
d3.select(`.rect-${index}`).attr("fill", lineColor); | |
let xOffset = x(index) + x.bandwidth() / 2; | |
var date = new Date(chartData.date[index]); | |
var dateString = date ? dateFormat(date) : ""; | |
hoverLine | |
.attr("opacity", 0.65) | |
.attr("x1", xOffset) | |
.attr("x2", xOffset) | |
.attr("y1", yLine(chartData[selectedData[activeProp]][index]) + 40) | |
.attr( | |
"y2", | |
y(chartData[selectedData.bar][index]) + | |
height - | |
rectHeight - | |
margin.bottom - | |
115 | |
); | |
hoverText | |
.attr("opacity", 1) | |
.attr("x", xOffset) | |
.attr("y", y(chartData[selectedData.bar][index]) - 40) | |
.text(priceFormat(chartData[selectedData.bar][index])) | |
.raise(); | |
subtitleHoverText | |
.attr("opacity", 0.65) | |
.attr("x", xOffset) | |
.attr("y", y(chartData[selectedData.bar][index]) - 20) | |
.raise(); | |
dateText | |
.attr("opacity", 1) | |
.attr("x", xOffset) | |
.attr("y", height - margin.bottom + 20) | |
.text(dateString); | |
hoverCircle | |
.attr("opacity", 1) | |
.attr("cx", xOffset) | |
.attr("cy", yLine(chartData[selectedData[activeProp]][index])); | |
lineValueText | |
.attr("opacity", 1) | |
.attr("x", xOffset) | |
.attr("y", yLine(chartData[selectedData[activeProp]][index]) - 40) | |
.text(apyFormat(chartData[selectedData[activeProp]][index] / 100)); | |
var apyTitle = apyToggle ? "NET APY" : "SLICE APY"; | |
lineValueTextSubtitle | |
.attr("opacity", 0.65) | |
.attr("x", xOffset) | |
.attr("y", yLine(chartData[selectedData[activeProp]][index]) - 20) | |
.text(apyTitle); | |
} | |
function clearHover() { | |
hoverLine.attr("opacity", 0); | |
subtitleHoverText.attr("opacity", 0); | |
hoverText.attr("opacity", 0).text(); | |
dateText.attr("opacity", 0).text(); | |
hoverCircle.attr("opacity", 0); | |
lineValueText.attr("opacity", 0).text(); | |
lineValueTextSubtitle.attr("opacity", 0); | |
d3.selectAll(".bar").attr("fill", rectColor); | |
} | |
return svg.node(); | |
} | |
); | |
main.variable(observer("hoverScale")).define("hoverScale", ["d3","chartData","margin","width"], function(d3,chartData,margin,width){return( | |
d3 | |
.scaleLinear() | |
.domain([0, chartData.trancheAApy.length - 1]) | |
.range([margin.left, width - margin.right]) | |
.clamp(true) | |
)}); | |
main.variable(observer("apyFormat")).define("apyFormat", ["d3"], function(d3){return( | |
d3.format(".2%") | |
)}); | |
main.variable(observer("dateFormat")).define("dateFormat", ["d3"], function(d3){return( | |
d3.timeFormat("%b %d") | |
)}); | |
main.variable(observer("chartData")).define("chartData", ["apiData"], function(apiData){return( | |
apiData.result.chartData | |
)}); | |
main.variable(observer()).define(["htl"], function(htl){return( | |
htl.html`<style> | |
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap'); | |
div, input, button { | |
font-family: 'Inter', sans-serif; | |
} | |
</style>` | |
)}); | |
main.variable(observer("apiData")).define("apiData", ["d3"], function(d3){return( | |
d3.json("https://chart-tranche.herokuapp.com/chart") | |
)}); | |
main.variable(observer("priceFormat")).define("priceFormat", ["d3"], function(d3){return( | |
d3.format("($,.2f") | |
)}); | |
main.variable(observer("line")).define("line", ["d3","x","yLine"], function(d3,x,yLine){return( | |
d3 | |
.line() | |
.x((d, i) => x(i) + x.bandwidth() / 2) | |
.y((d, i) => yLine(d)) | |
.curve(d3.curveCatmullRom) | |
)}); | |
main.variable(observer("x")).define("x", ["d3","chartData","margin","width"], function(d3,chartData,margin,width){return( | |
d3 | |
.scaleBand() | |
.domain(d3.range(chartData.date.length)) | |
.range([margin.left, width - margin.right]) | |
.padding(0.1) | |
)}); | |
main.variable(observer("yLine")).define("yLine", ["d3","chartData","rectHeight"], function(d3,chartData,rectHeight){return( | |
d3 | |
.scaleLinear() | |
.domain([0, d3.max([d3.max(chartData.netAApy), d3.max(chartData.netBApy)])]) | |
.range([rectHeight, 0]) | |
)}); | |
main.variable(observer("y")).define("y", ["d3","chartData","rectHeight"], function(d3,chartData,rectHeight){return( | |
d3 | |
.scaleLinear() | |
.domain([ | |
0, | |
d3.max([ | |
d3.max(chartData.trancheAValueUSD), | |
d3.max(chartData.trancheBValueUSD) | |
]) | |
]) | |
.range([rectHeight, 0]) | |
)}); | |
main.variable(observer("lineColor")).define("lineColor", function(){return( | |
"#7277FF" | |
)}); | |
main.variable(observer("lineColorInactive")).define("lineColorInactive", function(){return( | |
"#342964" | |
)}); | |
main.variable(observer("rectColor")).define("rectColor", function(){return( | |
"#33315F" | |
)}); | |
main.variable(observer("rectHeight")).define("rectHeight", function(){return( | |
150 | |
)}); | |
main.variable(observer("height")).define("height", function(){return( | |
600 | |
)}); | |
main.variable(observer("margin")).define("margin", function(){return( | |
{ top: 60, right: 100, bottom: 50, left: 30 } | |
)}); | |
return main; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment