File indexing completed on 2024-05-19 06:00:30
0001 0002 function makeLineChart(dataset, xName, yObjs, axisLables) { 0003 var chartObj = {}; 0004 0005 var color = d3.scaleOrdinal().range(["#1F77B4", "#aec7e8", "#ff7f0e", "#ffbb78", "#2ca02c" 0006 ,"#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5" 0007 ,"#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f" 0008 ,"#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5" 0009 ,"#393b79","#5254a3","#6b6ecf","#9c9ede","#637939" 0010 ,"#b5cf6b","#cedb9c","#8c6d31","#bd9e39","#e7ba52" 0011 ,"#ad494a","#d6616b","#7b4173","#a55194","#ce6dbd"]); 0012 chartObj.xAxisLable = axisLables.xAxis; 0013 chartObj.yAxisLable = axisLables.yAxis; 0014 /* 0015 yObjsects format: 0016 {y1:{column:'',name:'name',color:'color'},y2} 0017 */ 0018 0019 chartObj.data = dataset; 0020 chartObj.margin = {top: 15, right: 60, bottom: 30, left: 50}; 0021 chartObj.width = 650 - chartObj.margin.left - chartObj.margin.right; 0022 chartObj.height = 480 - chartObj.margin.top - chartObj.margin.bottom; 0023 0024 // So we can pass the x and y as strings when creating the function 0025 chartObj.xFunct = function(d){return d[xName]}; 0026 0027 // For each yObjs argument, create a yFunction 0028 function getYFn(column) { 0029 return function (d) { 0030 return d[column]; 0031 }; 0032 } 0033 0034 // Object instead of array 0035 chartObj.yFuncts = []; 0036 for (var y in yObjs) { 0037 yObjs[y].name = y; 0038 yObjs[y].yFunct = getYFn(yObjs[y].column); //Need this list for the ymax function 0039 chartObj.yFuncts.push(yObjs[y].yFunct); 0040 } 0041 0042 //Formatter functions for the axes 0043 chartObj.formatAsNumber = d3.format(".0f"); 0044 chartObj.formatAsDecimal = d3.format(".2f"); 0045 chartObj.formatAsCurrency = d3.format("$.2f"); 0046 chartObj.formatAsYYYYMM = d3.timeFormat("%Y-%m"); 0047 0048 chartObj.formatAsFloat = function (d) { 0049 if (d % 1 !== 0) { 0050 return d3.format(".2f")(d); 0051 } else { 0052 return d3.format(".0f")(d); 0053 } 0054 0055 }; 0056 0057 chartObj.xFormatter = chartObj.formatAsYYYYMM; 0058 chartObj.yFormatter = chartObj.formatAsFloat; 0059 0060 chartObj.bisectYear = d3.bisector(chartObj.xFunct).left; //< Can be overridden in definition 0061 0062 //Create scale functions 0063 chartObj.xScale = d3.scaleLinear().range([0, chartObj.width]).domain(d3.extent(chartObj.data, chartObj.xFunct)); 0064 //chartObj.xScale = d3.scaleTime().range([0, chartObj.width]).domain(d3.extent(chartObj.data, chartObj.xFunct)); 0065 0066 // Get the max of every yFunct 0067 chartObj.max = function (fn) { 0068 return d3.max(chartObj.data, fn); 0069 }; 0070 chartObj.yScale = d3.scaleLinear().range([chartObj.height, 0]).domain([0, d3.max(chartObj.yFuncts.map(chartObj.max))]); 0071 0072 chartObj.formatAsYear = d3.format(""); 0073 0074 //Create axis 0075 0076 chartObj.xAxis = d3.axisBottom(chartObj.xScale).tickFormat(chartObj.xFormatter); 0077 chartObj.yAxis = d3.axisLeft(chartObj.yScale).tickFormat(chartObj.yFormatter); 0078 0079 0080 // Build line building functions 0081 function getYScaleFn(yObj) { 0082 return function (d) { 0083 return chartObj.yScale(yObjs[yObj].yFunct(d)); 0084 }; 0085 } 0086 0087 for (var yObj in yObjs) { 0088 yObjs[yObj].line = d3.line().x(function (d) { 0089 return chartObj.xScale(chartObj.xFunct(d)); 0090 }).y(getYScaleFn(yObj)); 0091 } 0092 0093 0094 chartObj.svg; 0095 0096 // Change chart size according to window size 0097 chartObj.update_svg_size = function () { 0098 chartObj.width = parseInt(chartObj.chartDiv.style("width"), 10) - (chartObj.margin.left + chartObj.margin.right); 0099 0100 chartObj.height = parseInt(chartObj.chartDiv.style("height"), 10) - (chartObj.margin.top + chartObj.margin.bottom); 0101 0102 /* Update the range of the scale with new width/height */ 0103 chartObj.xScale.range([0, chartObj.width]); 0104 chartObj.yScale.range([chartObj.height, 0]); 0105 0106 if (!chartObj.svg) {return false;} 0107 0108 /* Else Update the axis with the new scale */ 0109 chartObj.svg.select('.x.axis').attr("transform", "translate(0," + chartObj.height + ")").call(chartObj.xAxis); 0110 chartObj.svg.select('.x.axis .label').attr("x", chartObj.width / 2); 0111 0112 chartObj.svg.select('.y.axis').call(chartObj.yAxis); 0113 chartObj.svg.select('.y.axis .label').attr("x", -chartObj.height / 2); 0114 0115 /* Force D3 to recalculate and update the line */ 0116 for (var y in yObjs) { 0117 yObjs[y].path.attr("d", yObjs[y].line); 0118 } 0119 0120 0121 d3.selectAll(".focus.line").attr("y2", chartObj.height); 0122 0123 chartObj.chartDiv.select('svg').attr("width", chartObj.width + (chartObj.margin.left + chartObj.margin.right)).attr("height", chartObj.height + (chartObj.margin.top + chartObj.margin.bottom)); 0124 0125 chartObj.svg.select(".overlay").attr("width", chartObj.width).attr("height", chartObj.height); 0126 return chartObj; 0127 }; 0128 0129 chartObj.bind = function (selector) { 0130 chartObj.mainDiv = d3.select(selector); 0131 // Add all the divs to make it centered and responsive 0132 chartObj.mainDiv.append("div").attr("class", "inner-wrapper").append("div").attr("class", "outer-box").append("div").attr("class", "inner-box"); 0133 chartSelector = selector + " .inner-box"; 0134 chartObj.chartDiv = d3.select(chartSelector); 0135 d3.select(window).on('resize.' + chartSelector, chartObj.update_svg_size); 0136 chartObj.update_svg_size(); 0137 return chartObj; 0138 }; 0139 0140 // Render the chart 0141 chartObj.render = function () { 0142 //Create SVG element 0143 chartObj.svg = chartObj.chartDiv.append("svg").attr("class", "chart-area").attr("width", chartObj.width + (chartObj.margin.left + chartObj.margin.right)).attr("height", chartObj.height + (chartObj.margin.top + chartObj.margin.bottom)).append("g").attr("transform", "translate(" + chartObj.margin.left + "," + chartObj.margin.top + ")"); 0144 0145 // Draw Lines 0146 for (var y in yObjs) { 0147 yObjs[y].path = chartObj.svg.append("path").datum(chartObj.data).attr("class", "line").attr("d", yObjs[y].line).style("stroke", color(y)).attr("data-series", y).on("mouseover", function () { 0148 focus.style("display", null); 0149 }).on("mouseout", function () { 0150 focus.transition().delay(700).style("display", "none"); 0151 }).on("mousemove", mousemove); 0152 } 0153 0154 0155 // .selectAll("text") 0156 // .attr("y", 0) 0157 // .attr("x", 9) 0158 // .attr("dy", ".35em") 0159 // .attr("transform", "rotate(90)") 0160 // .style("text-anchor", "start") 0161 0162 // Draw Axis 0163 chartObj.svg.append("g").attr("class", "x axis").attr("transform", "translate(0," + chartObj.height + ")").call(chartObj.xAxis).append("text").attr("class", "label").attr("x", chartObj.width / 2).attr("y", 30).style("text-anchor", "middle").text(chartObj.xAxisLable); 0164 0165 0166 0167 chartObj.svg.append("g").attr("class", "y axis").call(chartObj.yAxis).append("text").attr("class", "label").attr("transform", "rotate(-90)").attr("y", -42).attr("x", -chartObj.height / 2).attr("dy", ".71em").style("text-anchor", "middle").text(chartObj.yAxisLable); 0168 0169 //Draw tooltips 0170 var focus = chartObj.svg.append("g").attr("class", "focus").style("display", "none"); 0171 0172 0173 for (var y in yObjs) { 0174 yObjs[y].tooltip = focus.append("g"); 0175 yObjs[y].tooltip.append("circle").attr("r", 5); 0176 yObjs[y].tooltip.append("rect").attr("x", 8).attr("y","-5").attr("width",22).attr("height",'0.75em'); 0177 yObjs[y].tooltip.append("text").attr("x", 9).attr("dy", ".35em"); 0178 } 0179 0180 // Year label 0181 focus.append("text").attr("class", "focus year").attr("x", 9).attr("y", 7); 0182 // Focus line 0183 focus.append("line").attr("class", "focus line").attr("y1", 0).attr("y2", chartObj.height); 0184 0185 //Draw legend 0186 var legend = chartObj.mainDiv.append('div').attr("class", "legend"); 0187 for (var y in yObjs) { 0188 series = legend.append('div'); 0189 series.append('div').attr("class", "series-marker").style("background-color", color(y)); 0190 series.append('p').text(y); 0191 yObjs[y].legend = series; 0192 } 0193 0194 // Overlay to capture hover 0195 chartObj.svg.append("rect").attr("class", "overlay").attr("width", chartObj.width).attr("height", chartObj.height).on("mouseover", function () { 0196 focus.style("display", null); 0197 }).on("mouseout", function () { 0198 focus.style("display", "none"); 0199 }).on("mousemove", mousemove); 0200 0201 return chartObj; 0202 function mousemove() { 0203 0204 var x0 = chartObj.xScale.invert(d3.mouse(this)[0]), i = chartObj.bisectYear(dataset, x0, 1), d0 = chartObj.data[i - 1], d1 = chartObj.data[i]; 0205 0206 try { 0207 var d = x0 - chartObj.xFunct(d0) > chartObj.xFunct(d1) - x0 ? d1 : d0; 0208 0209 } catch (e) { console.log(e); return;} 0210 minY = chartObj.height; 0211 for (var y in yObjs) { 0212 yObjs[y].tooltip.attr("transform", "translate(" + chartObj.xScale(chartObj.xFunct(d)) + "," + chartObj.yScale(yObjs[y].yFunct(d)) + ")"); 0213 yObjs[y].tooltip.select("text").text(chartObj.yFormatter(yObjs[y].yFunct(d))); 0214 minY = Math.min(minY, chartObj.yScale(yObjs[y].yFunct(d))); 0215 } 0216 0217 focus.select(".focus.line").attr("transform", "translate(" + chartObj.xScale(chartObj.xFunct(d)) + ")").attr("y1", minY); 0218 focus.select(".focus.year").text("Yearmonth: " + chartObj.xFormatter(chartObj.xFunct(d))); 0219 } 0220 0221 }; 0222 return chartObj; 0223 } 0224 0225