Skip to content Skip to sidebar Skip to footer

How To Draw A Rectangle With D3.js Based On 2 Diametrical Points And No Length Or Height Values?

I want to add a rectangle in my d3.js graph highlighting a specific data region. The problem is I don't want to specify a starting point and then a height and length. Rather I woul

Solution 1:

If you are just passing the x and y values of the two points, why not using a rect element itself? It's way shorter and easier than drawing a path as in your answer:

functiondrawRectanglePoints(x1,y1,x2,y2,container,thisClass){
    container.append("rect")
      .attr("x", x1).attr("y", y1)
      .attr("width", x2-x1).attr("height", y2-y1)
      .attr("class", thisClass).attr("id", thisId);
}

Here is your demo:

functiondrawRectanglePoints(x1,y1,x2,y2,container,thisClass, thisId){
  container.append("rect").attr("x", x1).attr("y", y1).attr("width", x2-x1).attr("height", y2-y1).attr("class", thisClass).attr("id", thisId);
}
    
    
functiondrawLine(x1,y1,x2,y2, svgContainer, thisClass, thisId){
    svgContainer.append("line")
    .attr("x1", x1)
    .attr("y1", y1)
    .attr("x2", x2)
    .attr("y2", y2)
    .attr("class", thisClass)
    .attr("id", thisId);
}
    
    
	// Set the dimensions of the canvas / graphvar margin = {top: 30, right: 20, bottom: 30, left: 50},
	width = 600 - margin.left - margin.right,
	height = 270 - margin.top - margin.bottom;

    // Adds the svg canvasvar svg = d3.select("#graph")
	.append("svg")
	.attr("width", width + margin.left + margin.right)
	.attr("height", height + margin.top + margin.bottom)
	.append("g")
	.attr("transform",
		"translate(" + margin.left + "," + margin.top + ")");

	// Parse the date / timevar parseDate = d3.time.format("%Y-%m-%d").parse;

	// Set the rangesvar x = d3.time.scale().range([0, width]);
	var y = d3.scale.linear().range([height, 0]);

	// Define the axesvar xAxis = d3.svg.axis().scale(x)
	.orient("bottom").ticks(5);

	var yAxis = d3.svg.axis().scale(y)
	.orient("left").ticks(5);

	// Define the linevar valueline = d3.svg.line()
	.x(function(d) { returnx(d.date); })
	.y(function(d) { returny(d.rate); })
	.interpolate("monotone");

	// Define the div for the tooltipvar div = d3.select("body").append("div")
	.attr("class", "tooltip")
	.style("opacity", 0);


	// Get the data// this is where you would get your data via ajax / read a file / whatevervar resData = JSON.parse('[{"date":"2016-09-23","rate":"11.0707","nbItems":"8"},{"date":"2016-09-24","rate":"12.0317","nbItems":"10"},{"date":"2016-09-25","rate":"14.6562","nbItems":"9"},{"date":"2016-09-26","rate":"12.9523","nbItems":"7"},{"date":"2016-09-27","rate":"11.8636","nbItems":"10"},{"date":"2016-09-28","rate":"14.1731","nbItems":"10"},{"date":"2016-09-30","rate":"14.3167","nbItems":"3"},{"date":"2016-10-01","rate":"14.8398","nbItems":"4"},{"date":"2016-10-02","rate":"10.2088","nbItems":"1"},{"date":"2016-10-03","rate":"12.1985","nbItems":"9"},{"date":"2016-10-04","rate":"16.0133","nbItems":"5"},{"date":"2016-10-05","rate":"15.4206","nbItems":"6"}]');
    var sigmaMin = 10; // our fictional lower bound of data highlightingvar sigma = 12.5;
	var sigmaMax = 15; // our fictional upper bound of data highlightingvar i = 0;
	var startDate = false;
	resData.forEach(function(d) {
		// console.log(d.date);
		d.date = parseDate(String(d.date));
		d.rate = +d.rate;
		d.nbItems = +d.nbItems;
		if(i === 0){
			startDate = d.date;
		}
		endDate = d.date;
		i++;
	});

    // Scale the range of the data
    x.domain(d3.extent(resData, function(d) { return d.date; }));
    y.domain([0, d3.max(resData, function(d) { return d.rate; })]);

    // Add the valueline path for the data
    svg.append("path")
    .attr("class", "line")
    .attr("d", valueline(resData));

    drawRectanglePoints(x(startDate), y(sigmaMax), x(endDate), y(sigmaMin), svg, 'sigmaRectangle','sigmaRectangle');
    drawLine(0, y(sigmaMin), 530, y(sigmaMin), svg, 'sigma_line', 'sigma_line_min');
    drawLine(0, y(sigma), 530, y(sigma), svg, 'sigma_line', 'sigma_line');
    drawLine(0, y(sigmaMax), 530, y(sigmaMax), svg, 'sigma_line', 'sigma_line_max');


    // Add the scatterplot
    svg.selectAll("dot")
    .data(resData)
    .enter().append("circle")
    .attr("r", function(d) { return d.nbItems+7; }) // make size of dots depending on nb items included in this day +7 for min value
    .attr("cx", function(d) { returnx(d.date); })
    .attr("cy", function(d) { returny(d.rate); })
    .attr("data-date", function(d) { return d.date; });

    // Add the X Axis
    svg.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + height + ")")
    .call(xAxis);

    // Add the Y Axis
    svg.append("g")
    .attr("class", "y axis")
    .call(yAxis);

functiondrawRectangle(x1,y1,x2,y2,container,thisClass){
  var width = x2 - x1, height = y2 - y1;
  container.append("rect").attr("x", x1).attr("y", y1).attr("width", width).attr("height", height).attr("class", thisClass);
}
<scriptsrc="http://d3js.org/d3.v3.min.js"></script><!-- dont do this inside an external css script --><styletype="text/css">#graph{
    color: red;
    width: 100%;
  }
  #graph path {
    stroke: blue;
    stroke-width: 4;
    fill: none;
  }
  #graph.axis path,
  #graph.axis line {
    fill: none;
    stroke: grey;
    stroke-width: 1;
    shape-rendering: crispEdges;
  }
  #graph circle{
    fill: rgba(200, 200, 200,0.7);
    cursor: pointer;
  }
  #graph#sigmaRectangle {
    stroke: transparent;
    stroke-width: 0;
    fill: rgba(200, 200, 200,0.3);
  }
  #graph.sigma_line{
    stroke: rgba(200, 200, 200,0.5);
    stroke-width: 1;
    fill: none;
  }

</style><h2>D3.js Highlight area with</h2><p>rect with two diametral points from your dataset</p><divid="graph"></div>

The only difference between this and your code is that this doesn't check for negative width/height values (but it doesn't matter, because you said that you're passing the top left as the first pair and the bottom right as the second). Besides that, it's worth mentioning that rect has nothing to do with D3, it's an SVG element and its specifications are provided by W3C.

Solution 2:

Update: Just use a plain svg rect object.

The trick is to build the rectangle yourself out of lines rather than to use the rect element provided by d3.js.

I use this function:

functiondrawRectanglePoints(x1, y1, x3, y3, svgContainer, thisClass, thisId){
    // The data for the rectanglevar lineData = [
    { "x": x1,   "y": y1}, // start at upper-left
    { "x": x3,  "y": y1},  // goto upper-right 
    { "x": x3,  "y": y3},  // goto lower-right
    { "x": x1,  "y": y3},  // goto lower-left
    { "x": x1,  "y": y1},  // go back to upper-left
    ];

    // accessor functionvar lineFunction = d3.svg.line()
    .x(function(d) { return d.x; })
    .y(function(d) { return d.y; })
    .interpolate("linear"); // draw straight lines, not curved// draw the linesvar lineGraph = svgContainer.append("path") // svgContainer is the svg element initialised already
    .attr("d", lineFunction(lineData)) // here we add our lines
    .attr("class", thisClass) // give the element a class (performant for css)
    .attr("id", thisId); // give the element an id (performant for js)
}

Usage:

drawRectanglePoints(
    x(startDate),
    y(sigmaMax),
    x(endDate),
    y(sigmaMin),
    svgContainer,  // this is the d3.js object of the initialized svg'sigmaRectangle',
    'sigmaRectangle'
);

Complete example:

functiondrawRectanglePoints(x1, y1, x3, y3, svgContainer, thisClass, thisId){
  // this uses two diametral points to draw the rectange instead of a point and width and height// The data for the rectanglevar lineData = [
    { "x": x1,   "y": y1},
    { "x": x3,  "y": y1},
    { "x": x3,  "y": y3},
    { "x": x1,  "y": y3},
    { "x": x1,  "y": y1},
  ];
    // accessor functionvar lineFunction = d3.svg.line()
    .x(function(d) { return d.x; })
    .y(function(d) { return d.y; })
    .interpolate("linear");

    // draw the linesvar lineGraph = svgContainer.append("path")
    .attr("d", lineFunction(lineData))
    .attr("class", thisClass)
    .attr("id", thisId);
}
functiondrawLine(x1,y1,x2,y2, svgContainer, thisClass, thisId){
    svgContainer.append("line")
    .attr("x1", x1)
    .attr("y1", y1)
    .attr("x2", x2)
    .attr("y2", y2)
    .attr("class", thisClass)
    .attr("id", thisId);
}
    
    
	// Set the dimensions of the canvas / graphvar margin = {top: 30, right: 20, bottom: 30, left: 50},
	width = 600 - margin.left - margin.right,
	height = 270 - margin.top - margin.bottom;

    // Adds the svg canvasvar svg = d3.select("#graph")
	.append("svg")
	.attr("width", width + margin.left + margin.right)
	.attr("height", height + margin.top + margin.bottom)
	.append("g")
	.attr("transform",
		"translate(" + margin.left + "," + margin.top + ")");

	// Parse the date / timevar parseDate = d3.time.format("%Y-%m-%d").parse;

	// Set the rangesvar x = d3.time.scale().range([0, width]);
	var y = d3.scale.linear().range([height, 0]);

	// Define the axesvar xAxis = d3.svg.axis().scale(x)
	.orient("bottom").ticks(5);

	var yAxis = d3.svg.axis().scale(y)
	.orient("left").ticks(5);

	// Define the linevar valueline = d3.svg.line()
	.x(function(d) { returnx(d.date); })
	.y(function(d) { returny(d.rate); })
	.interpolate("monotone");

	// Define the div for the tooltipvar div = d3.select("body").append("div")
	.attr("class", "tooltip")
	.style("opacity", 0);


	// Get the data// this is where you would get your data via ajax / read a file / whatevervar resData = JSON.parse('[{"date":"2016-09-23","rate":"11.0707","nbItems":"8"},{"date":"2016-09-24","rate":"12.0317","nbItems":"10"},{"date":"2016-09-25","rate":"14.6562","nbItems":"9"},{"date":"2016-09-26","rate":"12.9523","nbItems":"7"},{"date":"2016-09-27","rate":"11.8636","nbItems":"10"},{"date":"2016-09-28","rate":"14.1731","nbItems":"10"},{"date":"2016-09-30","rate":"14.3167","nbItems":"3"},{"date":"2016-10-01","rate":"14.8398","nbItems":"4"},{"date":"2016-10-02","rate":"10.2088","nbItems":"1"},{"date":"2016-10-03","rate":"12.1985","nbItems":"9"},{"date":"2016-10-04","rate":"16.0133","nbItems":"5"},{"date":"2016-10-05","rate":"15.4206","nbItems":"6"}]');
    var sigmaMin = 10; // our fictional lower bound of data highlightingvar sigma = 12.5;
	var sigmaMax = 15; // our fictional upper bound of data highlightingvar i = 0;
	var startDate = false;
	resData.forEach(function(d) {
		// console.log(d.date);
		d.date = parseDate(String(d.date));
		d.rate = +d.rate;
		d.nbItems = +d.nbItems;
		if(i === 0){
			startDate = d.date;
		}
		endDate = d.date;
		i++;
	});

    // Scale the range of the data
    x.domain(d3.extent(resData, function(d) { return d.date; }));
    y.domain([0, d3.max(resData, function(d) { return d.rate; })]);

    // Add the valueline path for the data
    svg.append("path")
    .attr("class", "line")
    .attr("d", valueline(resData));

    drawRectanglePoints(x(startDate), y(sigmaMax), x(endDate), y(sigmaMin), svg, 'sigmaRectangle','sigmaRectangle');
    drawLine(0, y(sigmaMin), 530, y(sigmaMin), svg, 'sigma_line', 'sigma_line_min');
    drawLine(0, y(sigma), 530, y(sigma), svg, 'sigma_line', 'sigma_line');
    drawLine(0, y(sigmaMax), 530, y(sigmaMax), svg, 'sigma_line', 'sigma_line_max');


    // Add the scatterplot
    svg.selectAll("dot")
    .data(resData)
    .enter().append("circle")
    .attr("r", function(d) { return d.nbItems+7; }) // make size of dots depending on nb items included in this day +7 for min value
    .attr("cx", function(d) { returnx(d.date); })
    .attr("cy", function(d) { returny(d.rate); })
    .attr("data-date", function(d) { return d.date; });

    // Add the X Axis
    svg.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + height + ")")
    .call(xAxis);

    // Add the Y Axis
    svg.append("g")
    .attr("class", "y axis")
    .call(yAxis);
<scriptsrc="http://d3js.org/d3.v3.min.js"></script><!-- dont do this inside an external css script --><styletype="text/css">#graph{
    color: red;
    width: 100%;
  }
  #graph path {
    stroke: blue;
    stroke-width: 4;
    fill: none;
  }
  #graph.axis path,
  #graph.axis line {
    fill: none;
    stroke: grey;
    stroke-width: 1;
    shape-rendering: crispEdges;
  }
  #graph circle{
    fill: rgba(200, 200, 200,0.7);
    cursor: pointer;
  }
  #graph#sigmaRectangle {
    stroke: transparent;
    stroke-width: 0;
    fill: rgba(200, 200, 200,0.3);
  }
  #graph.sigma_line{
    stroke: rgba(200, 200, 200,0.5);
    stroke-width: 1;
    fill: none;
  }

</style><h2>D3.js Highlight area with</h2><p>rect with two diametral points from your dataset</p><divid="graph"></div>

Post a Comment for "How To Draw A Rectangle With D3.js Based On 2 Diametrical Points And No Length Or Height Values?"