function lineChart() { var margin = {top: 20, right: 20, bottom: 20, left: 20} , width = 760 , height = 120 , xValue = "date" , yValue = "val" , yText = "Number of Listings" , nestValue = "area" , colorScale = d3.scaleSequential(d3.interpolateRainbow) , xScale = d3.scaleTime() , yScale = d3.scaleLinear() , xAxis = d3.axisBottom(xScale).tickSize(6, 0) , yAxis = d3.axisLeft(yScale) , line = d3.line().curve(d3.curveMonotoneX).x(X).y(Y) , voronoi = d3.voronoi().x(X).y(Y) , hoverText; function chart(selection) { selection.each(function(data) { // Set Voronoi to User Inputed Margins and Height voronoi.extent([[-margin.left, -margin.top], [width + margin.right, height-margin.bottom]]); // Update the x-scale. xScale .domain(d3.extent(data, function(d) { return d[xValue]; })) .range([0, width - margin.left - margin.right]); // Update the y-scale. yScale .domain([0, d3.max(data, function(d) { return d[yValue]; })]) .range([height - margin.top - margin.bottom, 0]); if(yScale.domain()[1] <= 1) { yAxis.tickFormat(d3.format(".0%")) } // The margins are changed by accessors yAxis.tickSize(-(width-margin.left-margin.right)); xAxis.tickSize(-(height)); // Update the color-scale colorScale .domain([0, d3.set(data.map(function(d) { return d[nestValue] })).values().length]) // Nest data for lines var nestedData = d3.nest() .key(function(d) { return d[nestValue] }) .entries(data); // Select the svg element, if it exists. var svg = d3.select(this).selectAll("svg").data([data]); // Otherwise, create the skeletal chart. var gEnter = svg.enter().append("svg") .attr("width", width) .attr("height", height) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); gEnter.append("g") .attr("class", "y axis") .transition() .call(yAxis); gEnter.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + yScale.range()[0] + ")") .transition() .call(xAxis); gEnter.append("defs").append("clipPath") .attr("id", "clip") .append("rect") .attr("width", width) .attr("height", height); gEnter.append("text") .attr("class", "y-axis-text") .attr("text-anchor", "middle") // this makes it easy to centre the text as the transform is applied to the anchor .attr("transform", "translate("+ (-margin.left/2) +","+((height-margin.bottom)/2)+")rotate(-90)") // text is drawn off the screen top left, move down and out and rotate .text(yText); hoverText = gEnter.append("text") .attr("class", "hover-text") .attr("text-anchor", "end") .attr("x", width - margin.left - margin.right); // Update the outer dimensions. svg.attr("width", width) .attr("height", height); // Update the inner dimensions. var g = svg.select("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); // Create the lines gEnter.append("g") .attr("class", "lines") .selectAll(".line") .data(nestedData, function(d) { return d.key }) .enter().append("path") .attr("class", "line") .style("clip-path", "url(#clip)") .style("stroke", function(d, i) { d.color = colorScale(i); return d.color }) .attr("d", function(d) { return line(d.values) }) var lines = svg.select("g").select(".lines").selectAll(".line") .data(nestedData, function(d) { return d.key }); // Enter new lines lines.enter().append("path") .attr("class", "line") .style("clip-path", "url(#clip)") .style("stroke", function(d, i) { d.color = colorScale(i); return d.color }) .attr("d", function(d) { return line(d.values) }); // Remove any old lines lines.exit().remove(); // Update the line path. lines .style("stroke", function(d, i) { d.color = colorScale(i); return d.color }) .transition() .attr("d", function(d) { return line(d.values) }); // Update the x-axis. g.select(".x") .attr("transform", "translate(0," + yScale.range()[0] + ")") .transition() .call(xAxis); // Update the x-axis. g.select(".y") .transition() .call(yAxis); // Brushing section var margin2 = {top: height-margin.bottom + 10, right: margin.right, bottom: 80, left: margin.left} , height2 = height - margin2.top - margin2.bottom , width2 = width - margin.left - margin.right , x2 = d3.scaleTime().range([0, width - margin.left - margin.right]).domain(xScale.domain()) , y2 = d3.scaleLinear().range([height2, 0]).domain(yScale.domain()) , xAxis2 = d3.axisBottom(x2) , contextLine = d3.line().curve(d3.curveMonotoneX).x(X2).y(Y2); var brush = d3.brushX() .extent([[0, 0], [width2, height2]]) .on("brush end", brushed); var context = gEnter.append("g") .attr("class", "context") .attr("transform", "translate(0," + margin2.top + ")"); context.append("rect") .attr("class","background-rect") .attr("width", width2) .attr("height", height2) context.append("text") .attr("class", "brush-text") .attr("transform","translate(0," + (height2 + 35) + ")") .text("Click and drag this section to select a timeframe."); context.append("g") .attr("class", "axis axis--grid") .attr("transform", "translate(0," + height2 + ")") .call(d3.axisBottom(x2) .ticks(d3.timeMonth, 12) .tickSize(-height2) .tickFormat(function() { return null; })) .selectAll(".tick") .classed("tick--minor", function(d) { return d.getMonth(); }); context.append("g") .attr("class", "context-x axis") .attr("transform", "translate(0," + height2 + ")"); context.append("g") .attr("class", "context-brush brush"); d3.select(".context-x").call(xAxis2); d3.select(".context-brush").call(brush); function brushed() { if (d3.event.sourceEvent && d3.event.sourceEvent.type === "zoom") return; // ignore brush-by-zoom var s = d3.event.selection || x2.range(); xScale.domain(s.map(x2.invert, x2)); // Update Lines d3.selectAll(".line") .attr("d", function(d) { return line(d.values) }); // Update Voronoi d3.selectAll(".voronoi-path") .data(voronoi.polygons(d3.merge(nestedData.map(function(d) { return d.values; })))) .attr("d", function(d) { return d ? "M" + d.join("L") + "Z" : null; }); // Update Axis d3.select(".x").call(xAxis.ticks(6)); } // Ends Brushing Section // Voronoi Section // Create the Voronoi gEnter.append("g") .attr("class", "voronoi") .selectAll("path") .data(voronoi.polygons(d3.merge(nestedData.map(function(d) { return d.values; })))) .enter().append("path") .attr("class", "voronoi-path") .attr("d", function(d) { return d ? "M" + d.join("L") + "Z" : null; }) .on("mouseenter", mouseover) .on("mouseleave", mouseout); // Voronoi Selection var voronoiSelection = svg.select("g").select(".voronoi").selectAll(".voronoi-path") .data(voronoi.polygons(d3.merge(nestedData.map(function(d) { return d.values; })))); // Enter new voroni voronoiSelection.enter().append("path") .attr("class", "voronoi-path") .attr("d", function(d) { return d ? "M" + d.join("L") + "Z" : null; }) .on("mouseenter", mouseover) .on("mouseleave", mouseout); // Remove any old lines voronoiSelection.exit().remove(); // Update the line path. voronoiSelection .attr("d", function(d) { return d ? "M" + d.join("L") + "Z" : null; }); // Ends Voronoi Section }); } function mouseover(d) { console.log(d) d3.selectAll(".line") .filter(function(e) { return d.data[nestValue] == e.key }) .moveToFront() .transition() .style("stroke-width", 4) .style("stroke", "#000"); hoverText.text(d.data[nestValue]); } function mouseout(d) { d3.selectAll(".line") .filter(function(e) { return d.data[nestValue] == e.key }) .transition() .style("stroke-width", 1) .style("stroke", function(d, i) { return d.color }); hoverText.text(""); } // The x-accessor for the path generator; xScale ∘ xValue. function X(d) { return xScale(d[xValue]); } // The y-accessor for the path generator; yScale ∘ yValue. function Y(d) { return yScale(d[yValue]); } // The x-accessor for the path generator; x2 ∘ xValue. function X2(d) { return x2(d[xValue]); } // The y-accessor for the path generator; y2 ∘ yValue. function Y2(d) { return y2(d[yValue]); } chart.margin = function(_) { if (!arguments.length) return margin; margin = _; return chart; }; chart.width = function(_) { if (!arguments.length) return width; width = _; return chart; }; chart.height = function(_) { if (!arguments.length) return height; height = _; return chart; }; chart.x = function(_) { if (!arguments.length) return xValue; xValue = _; return chart; }; chart.y = function(_) { if (!arguments.length) return yValue; yValue = _; return chart; }; chart.yText = function(_) { if (!arguments.length) return yText; yText = _; return chart; }; chart.nest = function(_) { if (!arguments.length) return nestValue; nestValue = _; return chart; }; return chart; }