<!DOCTYPE html> <html> <body> <div id="vis"><svg></svg></div> <div id="text"></div> <style> div.CCMixed-tooltip { border-radius: 5px; visibility:hidden; background: rgba(255,255,255,0.9); position: absolute; padding: 8px; box-shadow: 0px 0px 5px #888888; font-family: Arial, serif; font-size: 12px; color: #777; } .CCMixed-axis, .legend { font-family: Arial, serif; font-size: 12px; fill: #777; } .CCMixed-axis path, .CCMixed-axis line { fill: none; stroke: #DDD; stroke-width: 2; } </style> <script src="http://d3js.org/d3.v3.min.js"></script> <script src="http://code.jquery.com/jquery-3.1.1.min.js"></script> <script> var CCMixedChart = {}; CCMixedChart.draw = function(elem, config){ var colorFunction = config.colorFunction; var canvas = d3.select("#" + elem); canvas.select("svg").selectAll("*").remove(); var margin = {top: 20, right: 40, bottom: 40, left: 50}; var width = +config.width - margin.left - margin.right; var height = +config.height - margin.top - margin.bottom - config.addtionalXAxisSpace; var svg = canvas.select("svg") .attr("width", config.width) .attr("height", config.height + config.addtionalXAxisSpace) .append("g").attr("class", "canvas") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); // draw legends if(config.showLegend){ height = height - drawLegends() * 20 - 10; } var x = d3.scale.ordinal().rangeRoundBands([0, width], 0.5).domain(config.xAxis); var yLeftMax = 0; var yRightMax = 0; for(var i=0; i<config.series.length; i++){ config.series[i].visible = true; for(var j=0; j<config.series[i].data.length; j++){ if(config.series[i].yAxis == "left" && yLeftMax < config.series[i].data[j]){ yLeftMax = config.series[i].data[j]; } if(config.series[i].yAxis == "right" && yRightMax < config.series[i].data[j]){ yRightMax = config.series[i].data[j]; } } } var yLeft = d3.scale.linear().range([height, 0]).domain([0, yLeftMax]); var yRight = d3.scale.linear().range([height, 0]).domain([0, yRightMax]); // draw xAxis drawXAxis(x); // draw left yAxis drawYAxis(yLeft, "left", config.yAxisLeft); drawYAxis(yRight, "right", config.yAxisRight); // draw charts for(var i=0; i<config.series.length; i++){ var chart = config.series[i]; if(chart.type == 'bar'){ drawBarChart(chart); } else if(chart.type == 'line'){ drawLineChart(chart) } } // draw tooltip var tooltip = canvas.append("div").attr("class", "CCMixed-tooltip"); // draw invisible bars for hover/click events svg.append("g").attr("class", "bars bars-action").selectAll(".bar-hover") .data(config.xAxis) .enter().append("rect") .attr("class", "bar-hover") .attr("x", function(d) { return x(d)-x.rangeBand()/2; }) .attr("y", 0) .attr("width", x.rangeBand()*2) .attr("height", height) .style("opacity", "0") .style("fill", "gold") .style("cursor", "pointer") .on("mouseover", function(d, i){mouseOver(this, d.replace(/ /g, '_'), i)}) .on("mouseout", function(d, i){mouseOut(this, d.replace(/ /g, '_'), i)}) .on("mousemove", function(){updateTooltipPos(d3.event)}) .on("click", function(d, i){ if(config.clickFunction){ config.clickFunction(config, d, i); } // stop the propagation of event d3.event.stopPropagation(); }); function drawLegends(){ var legend = svg.append("g").attr("class", "legends CCMixed-legend"); var lx = 0; var ly = 0; var rows = 1; for(var i=0; i<config.series.length; i++){ var chart = config.series[i]; if(chart.type == 'bar'){ drawBarLegend(chart); } else if(chart.type == 'line'){ drawLineLegend(chart) } } var tx = 0; if(rows == 1){ tx = (width - lx) / 2; } legend.attr("transform", "translate(" + tx + ", "+ (config.height+config.addtionalXAxisSpace-(rows+1)*20) + ")"); function drawBarLegend(chart){ var barLegend = legend.append("g") .attr("class", "legend legend-"+chart.label.replace(/ /g, '_')) .style("cursor", "pointer") .attr("transform", getLegendTransfrom(chart)) .style("fill", colorFunction(chart.colorKey)); barLegend.append("rect") .attr("x", 0) .attr("y", 0) .attr("width", 12) .attr("height", 12); barLegend.append("text") .attr("x", 10) .attr("y", 0) .attr("dy", "0.85em") .attr("dx", "0.4em") .style("text-anchor", "begin") .text(chart.label); barLegend.on("mouseover", function(){ svg.select(".bars-"+chart.label.replace(/ /g, '_')).selectAll(".bar") .style("stroke","gold").style("stroke-width", 3); }).on("mouseout", function(){ svg.select(".bars-"+chart.label.replace(/ /g, '_')).selectAll(".bar") .style("stroke-width", 0); }).on("click", function(){ toggleVisibility(chart); }); } function drawLineLegend(chart){ var lineLegend = legend.append("g") .attr("class", "legend legend-"+chart.label.replace(/ /g, '_')) .style("cursor", "pointer") .style("fill", colorFunction(chart.colorKey)) .attr("transform", getLegendTransfrom(chart)); lineLegend.append("circle") .attr("r", 4) .attr("cx", 6) .attr("cy", 6) lineLegend.append("line") .attr("x1", 0) .attr("y1", 6) .attr("x2", 12) .attr("y2", 6) .style("stroke", colorFunction(chart.colorKey)) .style("stroke-width", 2) lineLegend.append("text") .attr("x", 10) .attr("y", 0) .attr("dy", "0.85em") .attr("dx", "0.4em") .style("text-anchor", "begin") .text(chart.label) lineLegend.on("mouseover", function(d){ svg.select(".line-"+chart.label.replace(/ /g, '_')) .style("stroke-width", 4); svg.select(".circles-"+chart.label.replace(/ /g, '_')).selectAll(".circle") .attr("r", 6).style("stroke","gold").style("stroke-width", 2); }).on("mouseout", function(d){ svg.select(".line-"+chart.label.replace(/ /g, '_')) .style("stroke-width", 2); svg.select(".circles-"+chart.label.replace(/ /g, '_')).selectAll(".circle") .attr("r", 4).style("stroke-width", 0); }).on("click", function(){ toggleVisibility(chart); }); } function toggleVisibility (chart){ if(chart.type == "bar"){ var elem = svg.select(".bars-"+chart.label.replace(/ /g, '_')); toggle(elem, chart); } else if(chart.type == "line"){ var elem = svg.select(".line-"+chart.label.replace(/ /g, '_')); toggle(elem, chart); elem = svg.select(".circles-"+chart.label.replace(/ /g, '_')); toggle(elem, chart); } var showLeft = false; var showRight = false; for(var i=0; i<config.series.length; i++){ if(config.series[i].yAxis == "left" && config.series[i].visible){ showLeft = true; } if(config.series[i].yAxis == "right" && config.series[i].visible){ showRight = true; } } if(showLeft){ svg.select(".axis-left").selectAll(".tick").select("text").style("visibility", "visible"); svg.select(".axis-left").select(".title").style("visibility", "visible"); } else { svg.select(".axis-left").selectAll(".tick").select("text").style("visibility", "hidden"); svg.select(".axis-left").select(".title").style("visibility", "hidden"); } if(showRight){ svg.select(".axis-right").selectAll(".tick").select("text").style("visibility", "visible"); svg.select(".axis-right").select(".title").style("visibility", "visible"); } else { svg.select(".axis-right").selectAll(".tick").select("text").style("visibility", "hidden"); svg.select(".axis-right").select(".title").style("visibility", "hidden"); } function toggle(elem, chart){ if(elem.style("visibility") == "visible"){ elem.style("visibility", "hidden"); chart.visible = false; var legend = svg.select(".legends").select(".legend-"+chart.label.replace(/ /g, '_')).style("fill", "#777"); if(chart.type == "line"){ legend.select("line").style("stroke", "#777"); } } else { elem.style("visibility", "visible"); chart.visible = true; var legend = svg.select(".legends").select(".legend-"+chart.label.replace(/ /g, '_')).style("fill", colorFunction(chart.colorKey)); if(chart.type == "line"){ legend.select("line").style("stroke", colorFunction(chart.colorKey)); } } } }; function getLegendTransfrom(chart){ var translate = "translate(" + lx + "," + ly + ")"; lx = lx + chart.label.length * 5 + 30; if(chart.type == "line"){ lx = lx + 15; } else if(chart.type == "bar"){ lx = lx - 10; } if(lx + chart.label.length * 5 >= width){ lx = 0; ly = ly + 20; rows = rows + 1; } return translate; } return rows; } function drawBarChart(chart){ var yScale = yLeft; if(chart.yAxis == "right"){ yScale = yRight; } svg.append("g") .attr("class", function(d, i){return "bars bars-"+chart.label.replace(/ /g, '_');}) .selectAll(".bar") .data(chart.data) .enter().append("rect") .attr("class", function(d, i){return "bar bar-"+config.xAxis[i].replace(/ /g, '_');}) .attr("x", function(d, i) { return x(config.xAxis[i]); }) .attr("y", function(d) { return yScale(d); }) .attr("width", x.rangeBand()) .attr("height", function(d) { return height - yScale(d); }) .style("fill", colorFunction(chart.colorKey)); } function drawLineChart(chart){ var yScale = yLeft; if(chart.yAxis == "right"){ yScale = yRight; } var line = d3.svg.line() .x(function(d, i) { return x(config.xAxis[i]) + x.rangeBand()/2; }) .y(function(d) { return yScale(d); }); svg.append("g").append("path") .style("stroke", colorFunction(chart.colorKey)) .attr("class", function(d, i){return "line-"+chart.label.replace(/ /g, '_');}) .style("stroke-width", 2) .style("fill", "none") .attr("d", line(chart.data)); svg.append("g") .attr("class", function(d, i){return "circles circles-"+chart.label.replace(/ /g, '_');}) .selectAll(".circle") .data(chart.data) .enter().append("circle") .attr("class", function(d, i){return "circle circle-"+config.xAxis[i].replace(/ /g, '_');}) .attr("r", 4) .style("fill", colorFunction(chart.colorKey)) .attr("cx", function(d, i){return x(config.xAxis[i]) + x.rangeBand()/2;}) .attr("cy", function(d){return yScale(d);}); } function mouseOver(elem, d, i){ d3.select(elem).style("opacity", "0.3"); svg.select(".axis-x").select(".axis-"+d).style("font-weight","bold").style("font-size","14px"); svg.selectAll(".circles").select(".circle-"+d).attr("r", 7).style("stroke","gold").style("stroke-width",2); svg.selectAll(".bars").select(".bar-"+d).style("stroke","gold").style("stroke-width", 2); var html; if(config.tooltipFunction){ html = config.tooltipFunction(config, d, i); } else { html = getTooltips(d, i); } tooltip.html(html).style("visibility", "visible"); function getTooltips(d, i){ var html = d + "<br/>"; for(var j=0; j<config.series.length; j++){ html = html + "<b>" + config.series[j].label + "</b>: " + config.series[j].data[i] + "<br/>"; } return html; } }; function mouseOut(elem, d, i){ d3.select(elem).style("opacity", "0"); svg.select(".axis-x").select(".axis-"+d).style("font-weight","normal").style("font-size","12px"); svg.selectAll(".circles").select(".circle-"+d).attr("r", 4).style("stroke-width",0); svg.selectAll(".bars").select(".bar-"+d).style("stroke-width",0); tooltip.style("visibility", "hidden"); } function updateTooltipPos (e){ tooltip.style("top", (e.pageY + 15) + "px") .style("left", (e.pageX + 15) + "px"); }; function drawXAxis(x){ // draw x svg.append("g") .attr("class", "CCMixed-axis axis-x") .attr("transform", "translate(0," + height + ")") .call(customXAxis) .selectAll("text") .data(config.xAxis) .attr("class", function(d, i){return "axis-"+config.xAxis[i].replace(/ /g, '_');}) .style("text-anchor", "end") .attr("dy", "0.3em") .attr("dx", "-0.7em") .attr("transform", "rotate(-45)") .text(function(d, i){ // skip one if too many ticks //alert(config.xAxis.length); if(config.xAxis.length >= 10 && i % 2 == 1){ return ""; } else { return d } }); function customXAxis(g) { var xAxis = d3.svg.axis() .scale(x) .orient("bottom"); g.call(xAxis); g.select(".domain").remove(); } } function drawYAxis(y, orient, setting){ var axisY = svg.append("g") .attr("class", "CCMixed-axis axis-"+orient) .style("fill", colorFunction(setting.colorKey)) .call(customYAxis) if(orient == 'right'){ axisY.attr("transform", "translate(" + width + ", 0)") } if(setting.title){ var axisYLabel = axisY.append("text") .text(setting.title) .attr("class", "title") if(orient == "left"){ axisYLabel.attr("transform", "rotate(-90)") .attr("y", -30) .attr("x", -1*height/2) .attr("dx", setting.title.length * 0.25 + "em") .style("text-anchor", "end") } else { axisYLabel.attr("transform", "rotate(90)") .attr("y", -30) .attr("x", height/2) .attr("dx", -1 * setting.title.length * 0.25 + "em") .style("text-anchor", "begin") } } function customYAxis(g) { var yAxis = d3.svg.axis() .scale(y) .orient(orient) .tickSize(orient=="left"? -1*width : 0) .ticks(config.yAxisTicks); g.call(yAxis); g.select(".domain").remove(); g.selectAll(".tick:not(:first-of-type) line").attr("stroke", "#030303"); } } }; var colorFunction1 = function(key){ return key; } var clickFunction1 = function(config, d, i){ var html = d + "<br/>"; for(var j=0; j<config.series.length; j++){ html = html + "<b>" + config.series[j].label + "</b>: " + config.series[j].data[i] + "<br/>"; } $("#text").html(html); } $(document).ready(function(){ var vis = $("#vis"); var config = { "addtionalXAxisSpace": 0, // OPTIONAL. if the labels for xAxis is too long can add more space between xAxis and legend "width": 600, // width of canvas "height": 400, // height of canvas "showLegend": true, // OPTIONAL. "colorFunction": colorFunction1, // color function, accept a key and return a color, can use d3.scale.category20() "clickFunction": clickFunction1, // OPTIONAL. handles click event when clicking a day //"tooltipFunction": tooltipFunction // OPTIONAL. if not provided default function will be used "xAxis": ["1-Jul", "2-Jul", "3-Jul", "4-Jul", "5-Jul"], "xAxisData": [1, 2, 3, 4, 5], // OPTIONAL. can store additional data to be used by the click event. e.g. xAxis is the date string but xAxisData is the timestamp "yAxisTicks": 5, // number of horizontal lines "yAxisLeft": { // config the left y axis, MUST if any series need to use this axis "title": "Number of Non-responding Nodes", "colorKey": "cornflowerblue" }, "yAxisRight": { // config the right y axis, MUST if any series need to use this axis "title": "Percentage (%)", "colorKey": "yellowgreen" }, "series":[ // array of chart config. one element correspond to one chart. bar charts are NOT stacked and will OVERLAP { "colorKey": " cornflowerblue", "type": "bar", // chart type - bar or line "data": [10, 20, 50, 100, 30], "yAxis": "left", // associate the chart to left or right axis "label": "label 1" // used in legends and tooltips }, { "colorKey": "yellowgreen", "type": "line", "data": [10, 20, 50, 100, 30], "yAxis": "right", "label": "label 2" }, { "colorKey": "yellow", "type": "line", "data": [30, 24, 64, 40, 39], "yAxis": "right", "label": "label 3" } ] }; CCMixedChart.draw("vis", config); }); </script> </body> </html>