Last active
September 2, 2021 15:39
-
-
Save irajnazamdev/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