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