I am a beginner and I got a problem when I try to select some element in a lineChart.
I know there are so many elites, So I come here for some help :)
I find a brush demo in there and a linechart(sorry they don't allow me to insert 2 links because I don't have 10 reputations) demo Then I write the code blow:
<!DOCTYPE html>
<meta charset="utf-8">
<style type="text/css">
/* 13. Basic Styling with CSS */
/* Style the lines by removing the fill and applying a stroke */
.line {
fill: none;
stroke: #ffab00;
stroke-width: 3;
}
/* Style the dots by assigning a fill and stroke */
.dot {
fill: #1 fab40;
stroke: #fff;
}
.dot .selected {
stroke: red;
}
</style>
<!-- Body tag is where we will append our SVG and SVG objects-->
<body>
</body>
<!-- Load in the d3 library -->
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
// 2. Use the margin convention practice
var margin = {top: 20, right: 50, bottom: 20, left: 50}
, width = window.innerWidth - margin.left - margin.right // Use the window's width
, height = window.innerHeight - margin.top - margin.bottom; // Use the window's height
// The number of datapoints
var n = 21;
// 5. X scale will use the index of our data
var xScale = d3.scaleLinear()
.domain([0, n-1]) // input
.range([0, width]); // output
// 6. Y scale will use the randomly generate number
var yScale = d3.scaleLinear()
.domain([0, 1]) // input
.range([height, 0]); // output
// 7. d3's line generator
var line = d3.line()
.x(function(d, i) { return xScale(i); }) // set the x values for the line generator
.y(function(d) { return yScale(d.y); }) // set the y values for the line generator
.curve(d3.curveMonotoneX);// apply smoothing to the line
// 8. An array of objects of length N. Each object has key -> value pair, the key being "y" and the value is a random number
var dataset = d3.range(n).map(function(d) { return {"y": d3.randomUniform(1)() } });
// 1. Add the SVG to the page and employ #2
var svg = d3.select("body").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 + ")");
// 3. Call the x axis in a group tag
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale)); // Create an axis component with d3.axisBottom
// 4. Call the y axis in a group tag
svg.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(yScale)); // Create an axis component with d3.axisLeft
// 9. Append the path, bind the data, and call the line generator
svg.append("path")
.datum(dataset) // 10. Binds data to the line
.attr("class", "line") // Assign a class for styling
.attr("d", line); // 11. Calls the line generator
// 12. Appends a circle for each datapoint
var dot = svg.append("g")
.attr("class", "dot") // Assign a class for styling
.selectAll(".dot")
.data(dataset)
.enter().append("circle") // Uses the enter().append() method
.attr("r", 5)
.attr("cx", function(d, i) { return xScale(i) })
.attr("cy", function(d) { return yScale(d.y) });
var brush = svg.append("g")
.attr("class", "brush")
.call(d3.brush()
.extent([[0, 0], [width, height]])
.on("start brush end", brushed));
function brushed() {
var selection = d3.event.selection;
dot.classed("selected", selection && function(d) {
return selection[0][0] <= d.x && d.x < selection[1][0]
&& selection[0][1] <= d.y && d.y < selection[1][1];
});
}
</script>
I try to write the same code like this demo, But I can't selected any dot, Where is the problem?
Thanks a lot!
Rewrite your brushed function like this:
function brushed() {
var selection = d3.event.selection;
dot.classed("selected", selection && function(d,i) {
return selection[0][0] <= xScale(i) && xScale(i) < selection[1][0]
&& selection[0][1] <= yScale(d.y) && yScale(d.y) < selection[1][1];
});
}
Pay attention, we use xScale and yScale here, for correctly matching selected area and coordinates of dots. Working example is below:
// 2. Use the margin convention practice
var margin = {top: 20, right: 50, bottom: 20, left: 50}
, width = window.innerWidth - margin.left - margin.right // Use the window's width
, height = window.innerHeight - margin.top - margin.bottom; // Use the window's height
// The number of datapoints
var n = 21;
// 5. X scale will use the index of our data
var xScale = d3.scaleLinear()
.domain([0, n-1]) // input
.range([0, width]); // output
// 6. Y scale will use the randomly generate number
var yScale = d3.scaleLinear()
.domain([0, 1]) // input
.range([height, 0]); // output
// 7. d3's line generator
var line = d3.line()
.x(function(d, i) { return xScale(i); }) // set the x values for the line generator
.y(function(d) { return yScale(d.y); }) // set the y values for the line generator
.curve(d3.curveMonotoneX);// apply smoothing to the line
// 8. An array of objects of length N. Each object has key -> value pair, the key being "y" and the value is a random number
var dataset = d3.range(n).map(function(d) { return {"y": d3.randomUniform(1)() } });
// 1. Add the SVG to the page and employ #2
var svg = d3.select("body").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 + ")");
// 3. Call the x axis in a group tag
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale)); // Create an axis component with d3.axisBottom
// 4. Call the y axis in a group tag
svg.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(yScale)); // Create an axis component with d3.axisLeft
// 9. Append the path, bind the data, and call the line generator
svg.append("path")
.datum(dataset) // 10. Binds data to the line
.attr("class", "line") // Assign a class for styling
.attr("d", line); // 11. Calls the line generator
// 12. Appends a circle for each datapoint
var dot = svg.append("g")
.attr("class", "dot") // Assign a class for styling
.selectAll(".dot")
.data(dataset)
.enter().append("circle") // Uses the enter().append() method
.attr("r", 5)
.attr("cx", function(d, i) { return xScale(i) })
.attr("cy", function(d) { return yScale(d.y) });
var brush = svg.append("g")
.attr("class", "brush")
.call(d3.brush()
.extent([[0, 0], [width, height]])
.on("start brush", brushed)
.on("end", getSelectedDots)
);
var selectedDots = [];
function getSelectedDots() {
var selection = d3.event.selection;
dot.classed("selected", selection && function(d,i) {
var isSelectedDot = selection[0][0] <= xScale(i) && xScale(i) < selection[1][0]
&& selection[0][1] <= yScale(d.y) && yScale(d.y) < selection[1][1];
if (isSelectedDot) {
selectedDots.push(d);
}
return isSelectedDot;
});
console.log('selectedDots ', selectedDots);
}
function brushed() {
var selection = d3.event.selection;
dot.classed("selected", selection && function(d,i) {
return selection[0][0] <= xScale(i) && xScale(i) < selection[1][0]
&& selection[0][1] <= yScale(d.y) && yScale(d.y) < selection[1][1];
});
}
/* 13. Basic Styling with CSS */
/* Style the lines by removing the fill and applying a stroke */
.line {
fill: none;
stroke: #ffab00;
stroke-width: 3;
}
/* Style the dots by assigning a fill and stroke */
.dot {
fill: #1fab40;
stroke: #fff;
}
.dot .selected {
stroke: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.11.0/d3.min.js"></script>
Related
I tried to figure out the difference between 'd3.event.pageX' & 'd3.mouse(this)[0]'.
I guessed both are same but,
when I console.log both,
the value was different by '8' in my code.
var height=600;
var width=600;
var graphgap=60;
d3.csv('./details.csv').then(function(data){
var svg =d3.select('section').append('svg')
.attr('width',600).attr('height',600)
.on('mousemove',mousemove)
drawrect(data);
})
function drawrect(data){
let bars=d3.select('svg').selectAll('rect').data(data);
bars.enter().append('rect').classed('bargraph',true)
.attr('x',function(d,i){return (i+1)*graphgap})
.attr('y',function(d){return height-(d.Age)*5})
.attr('width',55)
.attr('height',function(d){return (d.Age)*(5)})
}
function mousemove(){
let mouselocation =[];
d3.select('svg').append('text')
.text(d3.event.pageX)
.attr('x',d3.event.pageX)
.attr('y',d3.event.pageY)
console.log(d3.event.pageX)
console.log(d3.mouse(this)[0])
}
So, I think these two are two different things.
Can anyone let me know why it makes a difference?
The reason why I tried to figure this out is because I was re-writing the code below.
<script>
// set the dimensions and margins of the graph
var margin = {top: 10, right: 30, bottom: 30, left: 60},
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.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 + ")");
//Read the data
d3.csv("https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/data_IC.csv",function(data) {
// Add X axis --> it is a date format
var x = d3.scaleLinear()
.domain([1,100])
.range([ 0, width ]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add Y axis
var y = d3.scaleLinear()
.domain([0, 13])
.range([ height, 0 ]);
svg.append("g")
.call(d3.axisLeft(y));
// This allows to find the closest X index of the mouse:
var bisect = d3.bisector(function(d) { return d.x; }).left;
// Create the circle that travels along the curve of chart
var focus = svg
.append('g')
.append('circle')
.style("fill", "none")
.attr("stroke", "black")
.attr('r', 8.5)
.style("opacity", 0)
// Create the text that travels along the curve of chart
var focusText = svg
.append('g')
.append('text')
.style("opacity", 0)
.attr("text-anchor", "left")
.attr("alignment-baseline", "middle")
// Create a rect on top of the svg area: this rectangle recovers mouse position
svg
.append('rect')
.style("fill", "none")
.style("pointer-events", "all")
.attr('width', width)
.attr('height', height)
.on('mouseover', mouseover)
.on('mousemove', mousemove)
.on('mouseout', mouseout);
// Add the line
svg
.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
.attr("d", d3.line()
.x(function(d) { return x(d.x) })
.y(function(d) { return y(d.y) })
)
// What happens when the mouse move -> show the annotations at the right positions.
function mouseover() {
focus.style("opacity", 1)
focusText.style("opacity",1)
}
function mousemove() {
// recover coordinate we need
var x0 = x.invert(d3.mouse(this)[0]);
var i = bisect(data, x0, 1);
selectedData = data[i]
focus
.attr("cx", x(selectedData.x))
.attr("cy", y(selectedData.y))
focusText
.html("x:" + selectedData.x + " - " + "y:" + selectedData.y)
.attr("x", x(selectedData.x)+15)
.attr("y", y(selectedData.y))
}
function mouseout() {
focus.style("opacity", 0)
focusText.style("opacity", 0)
}
})
</script>
In documentation is written:
While you can use the native event.pageX and event.pageY, it is often
more convenient to transform the event position to the local
coordinate system of the container that received the event using
d3.mouse, d3.touch or d3.touches.
d3.event
d3.mouse - uses local coordinate (without margin (60px))
d3.event.pageX - uses global coordinate (with margin (60px))
But local cordinate start on 68px. I guess 8 pixels is used to describe the y-axis.
First of all, I apologize for such a long post and my absolute beginner knowledge about D3 framework.
I have managed to draw the following chart using D3 framework.
However, I am not been able to accomplish the following things:
1) Cannot draw Y-Axis vertical sideline
2) Cannot make the Y-Axis ticks to align centered. At the moment they all are left-aligned
3) Cannot make the color of X-Axis baseline same as Y-Axis gridlines
4) Cannot make the label "Growth Target (7)" center-aligned Right now it is left-aligned
1) Y-Axis Vertical Sideline:
The text Growth Target (7) is within the chart. In order to make the gridlines to stop before this, I have used this (approach 1)
// ******************* << Custom Y axis grid lines Begins
var yGrid = svg.selectAll(".hgrid")
.data(yTicks)
.enter()
.append("g")
.attr("transform", function(d){
return "translate(0, " + y(d) + ")"
});
yGrid.append("line")
.attr("class", "hgrid")
.attr("x1", 0)
// shorten growth target line width to give room for the label
.attr("x2", width - growth_target_width_adjusted);
yGrid.append("text")
.text(function(d){return d; })
.attr("class", "hgrid-label")
// update X and Y positions of the label
.attr("x", -15)
.attr("y", "0.3em");
d3.selectAll(".hgrid-label")
.each(function (d, i) {
d3.select(this).style("font-size", 13);
});
// ******************* Custom Y axis grid lines Ends >>
Rather than this (approach 2):
// gridlines in y axis function
function draw_yAxis_gridlines() {
return d3.axisLeft(y)
.tickValues(yTicks);
}
// add the Y grid lines
svg.append("g")
.attr("class", "grid axis yAxis")
.call(draw_yAxis_gridlines()
.tickSize(-width)
);
The above approach 2, however, creates the Y-Axis baseline but the gridlines are using the entire chart area and overlapping the text "Growth Target (7)".
Is there any way that I can control the width of each Y-Axis line using approach 2? If not, how can I draw the Y-Axis baseline using approach 1?
2) Y-Axis ticks to align center
Currently, the Y-Axis tick values (0, 5, 10...) are left-aligned. Is there a way to make them centered?
3) Color of X-Axis baseline same as Y-Axis gridlines
In my chart above, the X-Axis color is black while the gridline colors are light gray. I need to make the X-Axis color light gray as well. What is the right way to do this?
4) Center aligning the label "Growth Target (7)"
What is the right way to make the two lines of the label centered-aligned? Right now the lines are aligned left to each other.
The desired output needs to be like this:
Here is the complete chart script that I have now:
function barColor(data_month, current_month) {
if( parseInt(data_month) >= current_month)
return "#008600";
else
return "#c4c4c4";
}
function draw_gp_chart(data_str) {
var chart_container = jQuery("#ecbg_unitary");
jQuery(chart_container).html("");
var w = parseInt(jQuery(chart_container).width());
var h = 375;
var barPadding = 2;
// set the dimensions and margins of the graph
var margin = {top: 30, right: 20, bottom: 30, left: 40};
var width = w - margin.left - margin.right;
var height = h - margin.top - margin.bottom;
var growth_target_width_adjusted = margin.left + margin.right;
var svg = d3.select("#ecbg_unitary")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + ", 10)");
var data = data_str;
// set the ranges (less width of chart to allow room for Growth Target label)
var x = d3.scaleBand().range([0, width - growth_target_width_adjusted]).padding(0.2);
var y = d3.scaleLinear().range([height, 0]);
// Scale the range of the data in the domains
x.domain(data.map(function (d) {
return d.month;
}));
var y_domain_upperBound = d3.max(data, function (d) {
return d.points;
});
y_domain_upperBound = Math.round(y_domain_upperBound / 10) * 10 + 10;
y.domain([0, y_domain_upperBound]);
// Create Y-Axis tick array to draw grid lines
var yTicks = [];
var tickInterval = 5;
for (var i = 0; i <= y_domain_upperBound; i = i + tickInterval) {
yTicks.push(i);
}
// gridlines in y axis function
function draw_yAxis_gridlines() {
return d3.axisLeft(y)
.tickValues(yTicks);
}
// ******************* << Growth Target line Begins
var targetGoalArr = [parseInt(jQuery("#ecbg_unitary_growth_target").val())];
var target = svg.selectAll(".targetgoal")
.data(targetGoalArr)
.enter()
.append("g")
.attr("transform", function(d){
return "translate(0, " + y(d) + ")"
});
target.append("line")
.attr("class", "targetgoal")
.attr("x1", 0)
// shorten growth target line width to give room for the label
.attr("x2", width - growth_target_width_adjusted);
// Adding two SVG text elements since SVG text element does not support text-wrapping.
// ref: https://stackoverflow.com/a/13254862/1496518
// Option 2: may be tried later - https://bl.ocks.org/mbostock/7555321
// Text element 1
target.append("text")
.text(function(d){return "Growth " })
.attr("class", "tglebal")
// update X position of the label
.attr("x", width - growth_target_width_adjusted + 5)
.attr("y", "-0.3em");
// Text element 2
target.append("text")
.text(function(d){return "Target (" + d + ")" })
.attr("class", "tglebal")
// update X position of the label
.attr("x", width - growth_target_width_adjusted + 5)
.attr("y", "1em");
d3.selectAll(".tglebal")
.each(function (d, i) {
d3.select(this).style("font-size", 12);
});
// ******************* Growth Target line Ends >>
// ******************* << Custom Y axis grid lines Begins
var yGrid = svg.selectAll(".hgrid")
.data(yTicks)
.enter()
.append("g")
.attr("transform", function(d){
return "translate(0, " + y(d) + ")"
});
yGrid.append("line")
.attr("class", "hgrid")
.attr("x1", 0)
// shorten growth target line width to give room for the label
.attr("x2", width - growth_target_width_adjusted);
yGrid.append("text")
.text(function(d){return d; })
.attr("class", "hgrid-label")
// update X and Y positions of the label
.attr("x", -15)
.attr("y", "0.3em");
d3.selectAll(".hgrid-label")
.each(function (d, i) {
d3.select(this).style("font-size", 13);
});
// ******************* Custom Y axis grid lines Ends >>
// append the rectangles for the bar chart
svg.selectAll("rect")
.data(data)
.enter().append("rect")
.attr("x", function (d) {
return x(d.month);
})
.attr("width", x.bandwidth())
.attr("y", function (d) {
return y(d.points);
})
.attr("height", function (d) {
return height - y(d.points);
})
//.attr("height", function(d) {return d.points;})
.attr("fill", function (d) {
return barColor(d.data_month_number, d.current_month_number)
});
// column labels
svg.selectAll(".colvalue")
.data(data)
.enter()
.append("text")
.attr("class", "colvalue")
.text(function (d) {
return d.points;
})
.attr("text-anchor", "middle")
.attr("x", function (d, i) {
return x(d.month) + x.bandwidth() / 2;
})
//.attr("x", function(d) { return x(d.month); })
.attr("y", function (d) {
//return h - (d.points * 4) - 10;
return y(d.points) - 10;
})
.attr("font-family", "Roboto")
.attr("font-size", "13px")
.attr("font-weight", "bold")
.attr("fill", "#606668");
// add the x Axis
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// text label (axis name) for the x axis
var xAxisNameHeightFromTop = height + margin.bottom + 20;
svg.append("g")
.attr("class", "x-axis-name")
.append("text")
.attr("transform", "translate(" + width / 2 + ", " + xAxisNameHeightFromTop + ")")
.style("text-anchor", "middle")
.text("MONTH");
// text label for the y axis
svg.append("g")
.attr("class", "y-axis-name")
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - margin.left)
.attr("x", 0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("SALES UNITS");
// add the Y grid lines
/*svg.append("g")
.attr("class", "grid axis yAxis")
.call(draw_yAxis_gridlines()
.tickSize(-width)
);*/
d3.selectAll(".yAxis>.tick>text")
.each(function (d, i) {
d3.select(this).style("font-size", 13);
});
}
I have a d3.v3.min.js histogram created using this as reference Histogram chart using d3 and I'd like to highlight in a separate plot (scatter plot) all the points that fall within one bar of this histogram. To this end I hook on the mouseover event of the rectangle to get the values within one bin. This works fine but I can't get their indices from the original input array:
var data = d3.layout.histogram().bins(xTicks)(values);
bar.append("rect")
.attr("x", 1)
.attr("width", (x(data[0].dx) - x(0)) - 1)
.attr("height", function(d) { return height - y(d.y); })
.attr("fill", function(d) { return colorScale(d.y) })
.on("mouseover", function (d, i) { console.log(d); });
d is an array containing all the values within the bin, and i is the bin index. I need the indices of the original data values I passed to the histogram function so that I can look them up in the other plot by index (as opposed to a binary search needed on the value).
Instead of just passing number values to the histogram generator you could create an array of objects carrying additional information:
// Generate a 1000 data points using normal distribution with mean=20, deviation=5
var f = d3.random.normal(20, 5);
// Create full-fledged objects instead of mere numbers.
var values = d3.range(1000).map(id => ({
id: id,
value: f()
}));
// Accessor function for the objects' value property.
var valFn = d => d.value;
// Generate a histogram using twenty uniformly-spaced bins.
var data = d3.layout.histogram()
.bins(x.ticks(20))
.value(valFn) // Provide accessor function for histogram generation
(values);
By providing an accessor function to the histogram generator you are then able to create the bins from this array of objects. Calling the histogram generator will consequently result in bins filled with objects instead of just raw numbers. In an event handler you are then able to access your data objects by reference. The objects will carry all the initial information, be it the id property as in my example, an index or anything else you put in them in the first place.
Have a look at the following snippet for a working demo:
var color = "steelblue";
var f = d3.random.normal(20, 5);
// Generate a 1000 data points using normal distribution with mean=20, deviation=5
var values = d3.range(1000).map(id => ({
id: id,
value: f()
}));
var valFn = d => d.value;
// A formatter for counts.
var formatCount = d3.format(",.0f");
var margin = {top: 20, right: 30, bottom: 30, left: 30},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var max = d3.max(values, valFn);
var min = d3.min(values, valFn);
var x = d3.scale.linear()
.domain([min, max])
.range([0, width]);
// Generate a histogram using twenty uniformly-spaced bins.
var data = d3.layout.histogram()
.bins(x.ticks(20))
.value(valFn)
(values);
var yMax = d3.max(data, function(d){return d.length});
var yMin = d3.min(data, function(d){return d.length});
var colorScale = d3.scale.linear()
.domain([yMin, yMax])
.range([d3.rgb(color).brighter(), d3.rgb(color).darker()]);
var y = d3.scale.linear()
.domain([0, yMax])
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var svg = d3.select("body").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 + ")");
var bar = svg.selectAll(".bar")
.data(data)
.enter().append("g")
.attr("class", "bar")
.attr("transform", function(d) { return "translate(" + x(d.x) + "," + y(d.y) + ")"; })
.on("mouseover", d => { console.log(d)});
bar.append("rect")
.attr("x", 1)
.attr("width", (x(data[0].dx) - x(0)) - 1)
.attr("height", function(d) { return height - y(d.y); })
.attr("fill", function(d) { return colorScale(d.y) });
bar.append("text")
.attr("dy", ".75em")
.attr("y", -12)
.attr("x", (x(data[0].dx) - x(0)) / 2)
.attr("text-anchor", "middle")
.text(function(d) { return formatCount(d.y); });
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
/*
* Adding refresh method to reload new data
*/
function refresh(values){
// var values = d3.range(1000).map(d3.random.normal(20, 5));
var data = d3.layout.histogram()
.value(valFn)
.bins(x.ticks(20))
(values);
// Reset y domain using new data
var yMax = d3.max(data, function(d){return d.length});
var yMin = d3.min(data, function(d){return d.length});
y.domain([0, yMax]);
var colorScale = d3.scale.linear()
.domain([yMin, yMax])
.range([d3.rgb(color).brighter(), d3.rgb(color).darker()]);
var bar = svg.selectAll(".bar").data(data);
// Remove object with data
bar.exit().remove();
bar.transition()
.duration(1000)
.attr("transform", function(d) { return "translate(" + x(d.x) + "," + y(d.y) + ")"; });
bar.select("rect")
.transition()
.duration(1000)
.attr("height", function(d) { return height - y(d.y); })
.attr("fill", function(d) { return colorScale(d.y) });
bar.select("text")
.transition()
.duration(1000)
.text(function(d) { return formatCount(d.y); });
}
// Calling refresh repeatedly.
setInterval(function() {
var values = d3.range(1000).map(id => ({
id: id,
value: f()
}));
refresh(values);
}, 2000);
body {
font: 10px sans-serif;
}
.bar rect {
shape-rendering: crispEdges;
}
.bar text {
fill: #999999;
}
.axis path, .axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.as-console-wrapper {
height: 20%;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
I have to create scatter plot with quadrants. I have looked at libraries like d3.js , high charts, nvd3 but I found only normal scatter charts.
Can someone suggest which js library will help me achieve this?
Thanks
D3.js allows this feature if you simply add negative values for the coordinates in the scatterplot. Just off the top of my hat, you could give the points their regular coordinates, just set the domains of your d3.scale function as to allow negative values. Just an example would be
var x = d3.scale.linear().range([0, width]).domain([d3.min(data), d3.max(data)]);
This sets the range of your scatterplot to the width you have selected, but allows all values to be accepted into the plot, regardless of them being positive or negative. As is very well explained here, scales fit to the size of the range, spreading the contents of the domain over the range. It is, however, not a requirement that said domain is completely positive.
If you check for the biggest absolute number in your data, you can format the domain likewise, therefore having the axes in the center of your plot, instead of them being misaligned.
Next, just add your axes like normal, only shift them to the middle of your canvas using x and y attributes.
Here is the upated Plunker link for creating scatter plot charts with quadrants through d3.js:-
http://plnkr.co/edit/yEfkN0tn7DPAypAvyWjD?p=preview
Code:
<script>
var svg = d3.select("#scatter"),
margin = {top: 20, right: 20, bottom: 30, left: 50},
width = +svg.attr("width"),
height = +svg.attr("height"),
domainwidth = width - margin.left - margin.right,
domainheight = height - margin.top - margin.bottom;
var x = d3.scaleLinear()
.domain(padExtent([1,5]))
.range(padExtent([0, domainwidth]));
var y = d3.scaleLinear()
.domain(padExtent([1,5]))
.range(padExtent([domainheight, 0]));
var g = svg.append("g")
.attr("transform", "translate(" + margin.top + "," + margin.top + ")");
g.append("rect")
.attr("width", width - margin.left - margin.right)
.attr("height", height - margin.top - margin.bottom)
.attr("fill", "#F6F6F6");
d3.json("data.json", function(error, data) {
if (error) throw error;
data.forEach(function(d) {
d.consequence = +d.consequence;
d.value = +d.value;
});
g.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("class", "dot")
.attr("r", 7)
.attr("cx", function(d) { return x(d.consequence); })
.attr("cy", function(d) { return y(d.value); })
.style("fill", function(d) {
if (d.value >= 3 && d.consequence <= 3) {return "#60B19C"} // Top Left
else if (d.value >= 3 && d.consequence >= 3) {return "#8EC9DC"} // Top Right
else if (d.value <= 3 && d.consequence >= 3) {return "#D06B47"} // Bottom Left
else { return "#A72D73" } //Bottom Right
});
g.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + y.range()[0] / 2 + ")")
.call(d3.axisBottom(x).ticks(5));
g.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + x.range()[1] / 2 + ", 0)")
.call(d3.axisLeft(y).ticks(5));
});
function padExtent(e, p) {
if (p === undefined) p = 1;
return ([e[0] - p, e[1] + p]);
}
</script>
I have an otherwise fine working grouped bar chart script to which I'm trying to add simple reference lines. The relevant code:
//Set up margins and dimensions according to http://bl.ocks.org/3019563
var margin = {top: 20, right: 10, bottom: 20, left: 30},
width = 810 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
/* Set up the primary x scale */
var x0 = d3.scale.ordinal()
.rangeRoundBands([0, width], .1)
.domain(data.map(function (d) {
return options.xPrimaryScaleAccessor(d);
}));
/* Set up the secondary x scale */
var x1 = d3.scale.ordinal()
.domain(xSecondaryScaleValues)
.rangeRoundBands([0, x0.rangeBand()]);
/* Set up the y scale as a linear (continous) scale with a total range of 0 - full height and a domain of 0-100 */
var y = d3.scale.linear()
.range([height, 0])
.domain([0, 100]);
/* Set up a color space of 20 colors */
var color = d3.scale.category20();
/* Set up the x axis using the primary x scale and align it to the bottom */
var xAxis = d3.svg.axis()
.scale(x0)
.orient("bottom");
/* Set up the y axis using the y scale and align it to the left */
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
/* Create an SVG element and append it to the body, set its dimensions, append a <g> element to
* it and apply a transform translating all coordinates according to the margins set up. */
var svg = d3.select(options.target).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 + ")");
//Create a space for definitions
var defs = svg.append("defs");
setupDropShadowFilter(defs, 3, 3, 3); //Sets up a gaussian blur filter with id 'drop-shadow'
/* Append a <g> element to the chart and turn it into a representation of the x axis */
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
/* Append a <g> element to the chart and turn it into a representation of the y axis */
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text(options.yLabel);
var dataArr = y.ticks(yAxis.ticks());
/* Draw the reference lines */
svg.selectAll("line")
.data(dataArr)
.enter().append("line")
.attr("x1", 0)
.attr("x2", width)
.attr("y1", y)
.attr("y2", y)
.style("stroke", "#ccc");
/* Set up the bar groups */
var group = svg.selectAll(".group")
.data(data)
.enter().append("g")
.attr("class", "g")
.attr("transform", function(d) { return "translate(" + x0(options.xPrimaryScaleAccessor(d)) + ",0)"; });
/* Draw the bars */
group.selectAll("rect")
.data(options.valueAccessor)
.enter().append("rect")
.attr("width", x1.rangeBand())
.attr("x", function(d) { return x1(d.label); })
.attr("y", function(d) { return y(d.value); })
.attr('rx', options.barCornerRadius)
.attr('ry', options.barCornerRadius)
.attr("height", function(d) { return height - y(d.value); })
.style("fill", function(d) { return getStripedPattern(defs, color(d.label)); //Sets up a pattern and returns its ID })//Todo: fill with pattern instead. see http://tributary.io/tributary/2929255
.style("filter", "url(#drop-shadow)");
/* Draw a legend */
var legend = svg.selectAll(".legend")
.data(xSecondaryScaleValues)
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + (xSecondaryScaleValues.length-i-.25) * (height/xSecondaryScaleValues.length) + ")"; });
legend.append("rect")
.attr("x", width - 9)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.attr("y", 9)
//.attr("dy", ".35em")
.attr("transform", "translate(" + (width - 6) + ",-8)rotate(-90)" )
.style("text-anchor", "start")
.text(function(d) { return d; });
EDIT: I have also tried to append rect elements instead with hardcoded coordinates and dimensions, but those also didn't make it to the DOM.
EDIT 2: More or less full code now included.
Basically, nothing happens. No lines are appended and there are no errors in the console. The dataArr is a plain array of numbers and y(number) is confirmed to return good values in the output range.
I think (and debug suggests) that the chain dies at the append() stage, possibly because .enter() return something useless.
Console log after .data():
Console log after .enter():
Console log after .append():
I've been stuck on this for a good while now, so grateful for any ideas about what may go wrong. I'm sure I'm overlooking something obvious...
The problem is that the code that generates the axes appends line elements to the SVG. As it is run before appending the reference lines, calling svg.selectAll("line").data(...) matches the existing lines with the data. There are more lines than data elements, so no new elements need to be added and the .enter() selection is empty.
There are a few ways to fix this. You could move the code that generates the reference lines further up. You add a g element that contains these lines. You could have a special class for these lines and adjust the selector accordingly. Or you could provide a custom matching function to .data().