Related
I am struggling trying to solve a problem with setting the yAxis tick marks so that I can "pin" a tick to a specific value. Please bear with my explanation, I will likely butcher the d3 terminology...
First, I am using this Grouped bar chart example as my reference, so please refer to that also.
Here is an image that more or less shows what I am trying to accomplish:
I want to make the y scale a percentage, and make "12" (red line) display "100%" (as a label?) and pin that to the value of the gray bar. The other ticks would be labelled as percentages also, with a linear scale (10%, 20%, etc). As the data will change, the y axis values will also change and the "100%" label will need to always align with the data corresponding to the gray bar value.
Think of this as a reference value that I can then compare the other bars to, and those bars can exceed 100%.
I am not sure of this is helpful, but the code that sets the y domain in question is as follows:
y.domain([0, d3.max(data, function(categorie) { return d3.max(categorie.values, function(d) { return d.value; }); })]);
How can I accomplish my goal?
I hope this makes sense.
Thanks!
Currently, the y scale you pass to yAxis maps your data values to svg dimensions.
data -> svg dimensions.
If you notice the yAxis labels its ticks with the actual data values, or the domain of the scale you give it.
So all you need to do is pass it a scale with percentage in its domain that goes to svg dimensions:
percentage -> svg dimensions
Here are two ways you can do that.
1) First convert the data to percentages and then make the scale. Then you use this scale for plotting and passing to the axis.
2) Leave the plotting alone and create a third scale that maps
percentage -> height and just pass this to yAxis. Notice this works because the scales are both linear.
Modifying the example you referenced I used the second method. I pasted the code for completeness and highlighted the part I modified. Note I made the "medium value" (grey bar) in the Student set 100%.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
</style>
<body>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x0 = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var x1 = d3.scale.ordinal();
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x0)
.tickSize(0)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var color = d3.scale.ordinal()
.range(["#ca0020","#f4a582","#d5d5d5","#92c5de","#0571b0"]);
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 + ")");
d3.json("data.json", function(error, data) {
var categoriesNames = data.map(function(d) { return d.categorie; });
var rateNames = data[0].values.map(function(d) { return d.rate; });
//=========================================================================
//=========================================================================
// Modified code here
//-------------------------------------------------------------------------
//-------------------------------------------------------------------------
//data -> percent - using the Student value of Medium to set to 100%
let dataToPercent = d3.scale.linear()
.domain( [0, data[0].values.filter( d => d.rate === "Medium" )[0].value] )
.range( [ 0, 100 ] )
// percent -> height
// Third scale just to pass to yAxis
let percentToHeight = d3.scale.linear()
.domain( [0, d3.max(data, function(categorie) {
return d3.max(categorie.values, function(d) {
return dataToPercent( d.value ); //only change is dataToPercent( d.value ) versus original return of d.value
})
})])
.range( [ height, 0] )
// set the yAxis with our new scale - use tickFormat to add percentage sign
yAxis
.scale( percentToHeight )
.tickFormat( d => d + "%" )
//add the red line
svg.append( "line" )
.attr( "x1", 0 )
.attr( "y1", percentToHeight( 100 ))
.attr( "x1", width )
.attr( "y2", percentToHeight( 100 ))
.attr( "stroke", "red" )
.attr( "stroke-width", "2px" )
//===========================================================================
//===========================================================================
// End modified code
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
x0.domain(categoriesNames);
x1.domain(rateNames).rangeRoundBands([0, x0.rangeBand()]);
y.domain([0, d3.max(data, function(categorie) { return d3.max(categorie.values, function(d) { return d.value; }); })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.style('opacity','0')
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.style('font-weight','bold')
.text("Value");
svg.select('.y').transition().duration(500).delay(1300).style('opacity','1');
var slice = svg.selectAll(".slice")
.data(data)
.enter().append("g")
.attr("class", "g")
.attr("transform",function(d) { return "translate(" + x0(d.categorie) + ",0)"; });
slice.selectAll("rect")
.data(function(d) { return d.values; })
.enter().append("rect")
.attr("width", x1.rangeBand())
.attr("x", function(d) { return x1(d.rate); })
.style("fill", function(d) { return color(d.rate) })
.attr("y", function(d) { return y(0); })
.attr("height", function(d) { return height - y(0); })
.on("mouseover", function(d) {
d3.select(this).style("fill", d3.rgb(color(d.rate)).darker(2));
})
.on("mouseout", function(d) {
d3.select(this).style("fill", color(d.rate));
});
slice.selectAll("rect")
.transition()
.delay(function (d) {return Math.random()*1000;})
.duration(1000)
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); });
//Legend
var legend = svg.selectAll(".legend")
.data(data[0].values.map(function(d) { return d.rate; }).reverse())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d,i) { return "translate(0," + i * 20 + ")"; })
.style("opacity","0");
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", function(d) { return color(d); });
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) {return d; });
legend.transition().duration(500).delay(function(d,i){ return 1300 + 100 * i; }).style("opacity","1");
});
</script>
This sets the 100% to the first grey bar, but you could extend this set each grey bar as 100% for its associated values, and then make the xaxis update on hover or something.
As to the red line, just append it to the svg (or any g element) with d3.append and modify its position as appropriate.
I am developing a two-way bar chart using d3. I want to add grid lines to the bar chart, how to customize those grid lines. I've used d3fc to draw the grid lines. It looks something like
var x = d3.scaleLinear()
.rangeRound([0, width])
var y = d3.scaleBand()
.rangeRound([0, height]).padding(0.5);
var xAxis = d3.axisBottom(x)
.ticks(8)
.tickSize(0)
.tickFormat(function(d){
return d3.format('.00s')(Math.abs(d)); // Use Math.abs() to get the absolute value
});
var yAxis = d3.axisLeft(y)
.ticks(5)
.tickSize(0);
//draw grid lines
const gridline = fc.annotationSvgGridline()
.xScale(x)
.yScale(y);
var svg = d3.select("#ageSexNotif").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 + ")");
x.domain([-d3.max(data, function(d){
return d.female
})*1.2,d3.max(data, function(d){
return d.female
})*1.2])
y.domain(data.map(function (d) {
return d.age;
}));
svg.append("g")
.attr("class", "x axis")
.call(xAxis)
.call(gridline);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
// .call(gridline);
var barsRight = svg.selectAll(".bar")
.data(data)
.enter().append("g")
barsRight.append("rect")
.attr("class", "bar")
.attr("x", function (d) {
return x(Math.min(0, d.female));
})
.attr("y", function (d) {
return y(d.age);
})
.attr("width", function (d) {
return Math.abs(x(d.female) - x(0));
})
.transition()
.duration(1500)
.delay(200)
.style("margin-top", "10px")
.attr("height", y.bandwidth())
.attr("fill", "#F293C9")
.attr("text", "label");
barsRight.append("text")
.attr("class", "label")
//y position of the label is halfway down the bar
.attr("y", function (d) {
return y(d.age) + y.bandwidth()- 6;
})
//x position is 3 pixels to the right of the bar
.attr("x", function (d) {
return x(d.female) + 10;
})
.text(function (d) {
return (d.female/1000)+'k';
})
.style("font-family", "Source Sans Pro")
.style("font-size", "14px")
.style("font-weight", "bold")
.attr("fill", "#F293C9");
var barsLeft = svg.selectAll(".bar2")
.data(data)
.enter().append("g")
barsLeft.append("rect")
.attr("class", "bar2")
.attr("x", function (d) {
return x(Math.min(0, -d.male));
})
.attr("y", function (d) {
return y(d.age);
})
.attr("width", function (d) {
return Math.abs(x(-d.male) - x(0));
})
.transition()
.duration(1500)
.delay(200)
.style("margin-top", "10px")
.attr("height", y.bandwidth())
.attr("fill", "#4880FF");
barsLeft.append("text")
.attr("class", "label")
.style("font-family", "Source Sans Pro")
.style("font-size", "14px")
.style("font-weight", "bold")
.attr("fill","#4880FF")
//y position of the label is halfway down the bar
.attr("y", function (d) {
return y(d.age) + y.bandwidth()- 6;
})
//x position is 3 pixels to the right of the bar
.attr("x", function (d) {
return x(-d.male) - 40;
})
.text(function (d) {
return (d.male/1000)+'k';
});
The result of my chart is
My chart should look like this
How to join the edges in x-axis and highlight the base axis as shown in the image? Any help for customizing the grid lines is appreciated.
Link to my example link
Thanks in advance!
You can add class name to your grid lines using attr('class', 'class-name'), and add your effect by CSS.
I've made some changes to your pen that you can see here.
The main changes:
join the edges in x-axis
If you remove the *1.2 multiplier from the domains and add .nice() then the x-axis will be squared off with the gridlines.
var x = d3.scaleLinear()
.rangeRound([0, width])
.domain([-d3.max(data, d => d.female), d3.max(data, d => d.female)])
.nice();
highlight the base axis
We can do this using an annotation line from d3fc.
const line = fc.annotationSvgLine()
.xScale(x)
.yScale(y)
.orient('vertical')
.label('')
.decorate(selection => {
selection.select('line')
.attr('stroke', 'black');
});
svg.datum([0]).call(line);
Here we are creating an annotation line using our scales. We set the line to be vertical and we remove the default label by replacing it with an empty string. After that we use the decorate function to colour the line black.
customizing the gridlines
We can control the opacity of the gridlines using a decorate function.
const opacityDecorate = selection => {
selection.attr('opacity', 0.2);
};
const gridline = fc.annotationSvgGridline()
.xScale(x)
.yScale(y)
.xDecorate(opacityDecorate)
.yDecorate(opacityDecorate);
Here we are using a decorate function to set the opacity for the both the horizontal and vertical gridlines. Alternatively you could also use different decorate functions to style the horizontal and vertical lines differently.
I hope this helps!
I have a function where that when a button is pressed (Several buttons the represent several animal types), that animal types SVG is updated with its corresponding data. I'm trying to replicate this zoom function but am having issues implementing it with my code. There are several SVGs that are used globally like this (one for each animal type):
let x = d3.scaleLinear()
.domain([0, 1000])
.range([ 0, width ]);
var xAxis = d3.axisBottom(x);
svgReptile.append("g")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
const yAxis = d3.scaleLinear()
.domain([0, 220])
.range([ height, 0])
svgReptile.append("g")
.call(d3.axisLeft(yAxis))
The function below is called when one of the animal buttons is pressed.
function update(animal, whatSVG, xAxis, yAxis, color) {
const points = whatSVG
.selectAll("circle")
.data(data);
points.enter()
.append("circle")
.attr("cx", function(d) {
return xAxis(d.state);
})
.attr("cy", function(d) {
return yAxis(d.percentage);
})
.merge(points)
.attr("r", 3)
.attr("cx", function(d) {
return xAxis(d.decade)
})
.attr("cy", function(d) {
return yAxis(d.count)
})
.style("fill", function (d) { return colour(d.animal) } );
points.exit()
.attr('r', 0)
.remove();
}
Question:
How can I implement a zoom feature that expands the x-axis when zoomed (or anything similar) like the one linked above?
I think you're looking for a 'brush zoom' from the last line of your question.
The following source code if from an example in a d3 graph gallery
The cross hair allows you to select an area to expand. If you follow the link there is a graph above it that is entitled "Zoom with axis" but it doesn't zoom in the way you've described, it just moves the axis, but doesn't enlarge the graph contents with it. Perhaps both will be useful!
Hope this helps
// set the dimensions and margins of the graph
var margin = {top: 10, right: 20, bottom: 20, left: 20},
width = 500 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// append the svg object to the body of the page
var Svg = d3.select("#brushZoom")
.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/iris.csv", function(data) {
// Add X axis
var x = d3.scaleLinear()
.domain([4, 8])
.range([ 0, width ]);
var xAxis = Svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add Y axis
var y = d3.scaleLinear()
.domain([0, 9])
.range([ height, 0]);
Svg.append("g")
.call(d3.axisLeft(y));
// Add a clipPath: everything out of this area won't be drawn.
var clip = Svg.append("defs").append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("width", width )
.attr("height", height )
.attr("x", 0)
.attr("y", 0);
// Color scale: give me a specie name, I return a color
var color = d3.scaleOrdinal()
.domain(["setosa", "versicolor", "virginica" ])
.range([ "#440154ff", "#21908dff", "#fde725ff"])
// Add brushing
var brush = d3.brushX() // Add the brush feature using the d3.brush function
.extent( [ [0,0], [width,height] ] ) // initialise the brush area: start at 0,0 and finishes at width,height: it means I select the whole graph area
.on("end", updateChart) // Each time the brush selection changes, trigger the 'updateChart' function
// Create the scatter variable: where both the circles and the brush take place
var scatter = Svg.append('g')
.attr("clip-path", "url(#clip)")
// Add circles
scatter
.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", function (d) { return x(d.Sepal_Length); } )
.attr("cy", function (d) { return y(d.Petal_Length); } )
.attr("r", 8)
.style("fill", function (d) { return color(d.Species) } )
.style("opacity", 0.5)
// Add the brushing
scatter
.append("g")
.attr("class", "brush")
.call(brush);
// A function that set idleTimeOut to null
var idleTimeout
function idled() { idleTimeout = null; }
// A function that update the chart for given boundaries
function updateChart() {
extent = d3.event.selection
// If no selection, back to initial coordinate. Otherwise, update X axis domain
if(!extent){
if (!idleTimeout) return idleTimeout = setTimeout(idled, 350); // This allows to wait a little bit
x.domain([ 4,8])
}else{
x.domain([ x.invert(extent[0]), x.invert(extent[1]) ])
scatter.select(".brush").call(brush.move, null) // This remove the grey brush area as soon as the selection has been done
}
// Update axis and circle position
xAxis.transition().duration(1000).call(d3.axisBottom(x))
scatter
.selectAll("circle")
.transition().duration(1000)
.attr("cx", function (d) { return x(d.Sepal_Length); } )
.attr("cy", function (d) { return y(d.Petal_Length); } )
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<div id="brushZoom"></div>
Current situation: I already have a small multiple visualization for my data. What it represents is the stress intensity over time for six different days. It plots the graphs correctly. Now I wanted to add dots on the existing graph if the person smoked at that time. I am reading a csv file which consists of date, time, stress level and whether the person smoked or not (so 1 if they did and -1 if they didn't). I am using d3 v4.
This is what I am currently getting but the red dots are obviously in the wrong spot because they are showing up places I don't even have data.
What I wanted was for the red dots to be on the graph and represent the times the user smoked.
Code:
<script>
var margin = {top: 8, right: 10, bottom: 2, left: 10},
width = 1160 - margin.left - margin.right,
height = 100 - margin.top - margin.bottom;
var parseDate = d3.timeParse("%H:%M:%S");
var x = d3.scaleTime()
.range([0, width]);
var y = d3.scaleLinear()
.range([height, 0]);
var area = d3.area()
.x(function (d) {
return x(d.time);
})
.y0(height)
.y1(function (d) {
return y(d.stress);
});
var line = d3.line()
.x(function (d) {
return x(d.time);
})
.y(function (d) {
return y(d.stress);
});
d3.csv("6000smokedData3.csv", type, function (error, data) {
// Nest data by date.
var dates = d3.nest()
.key(function (d) {
return d.date;
})
.entries(data);
// Compute the maximum stress per date, needed for the y-domain.
dates.forEach(function (s) {
s.maxPrice = d3.max(s.values, function (d) {
return d.stress;
});
});
// Compute the minimum and maximum time across dates.
// We assume values are sorted by time.
x.domain([
d3.min(dates, function (s) {
return s.values[0].time;
}),
d3.max(dates, function (s) {
return s.values[s.values.length - 1].time;
})
]);
// Add an SVG element for each date, with the desired dimensions and margin.
var svg = d3.select("body").selectAll("svg")
.data(dates)
.enter().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 + ")");
//Add the scatterplot
svg.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("r", 4)
.style("fill", function (d) {
return "red";
})
.attr("cx", function (d) {
if (d.smoked == 1) {
return x(d.time);
}
})
.attr("cy", function (d) {
if (d.smoked == 1) {
return y(d.stress);
}
});
// Add the X Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add the Y Axis
svg.append("g")
.call(d3.axisLeft(y));
// Add the area path elements. Note: the y-domain is set per element.
svg.append("path")
.attr("class", "area")
.attr("d", function (d) {
y.domain([0, d.maxPrice]);
return area(d.values);
});
// Add the line path elements. Note: the y-domain is set per element.
svg.append("path")
.attr("class", "line")
.attr("d", function (d) {
y.domain([0, d.maxPrice]);
return line(d.values);
});
// Add a small label for the date name.
svg.append("text")
.attr("x", width - 6)
.attr("y", height - 6)
.style("text-anchor", "end")
.text(function (d) {
return d.key;
});
});
function type(d) {
d.stress = +d.stress;
d.time = parseDate(d.time);
d.smoked = +d.smoked;
return d;
}
</script>
Few lines of csv file:
date,time,stress,smoked
2014-08-04,11:24:28,0.026191,-1
2014-08-04,11:24:29,0.026183,-1
2014-08-04,11:24:30,0.031845,-1
2014-08-04,11:24:31,0.01235,-1
Thank you
You're drawing the dots before you set the y scale for each element. I usually like to make small multiples inside of an each loop to avoid tricky things like. It looks like the y axis is also off - they should be different on each plot.
I have a question. How should I redo this example (http://bl.ocks.org/DStruths/9c042e3a6b66048b5bd4) which uses a .tsv file to instead utilize a script with literal data?
So far I have done the following: http://codepen.io/Balzzac/pen/MJorXw?editors=0010 , but nothing works.
My code:
var dataset = [here is 15000 raws of original data converted into JSON, using http://codebeautify.org/tsv-to-json-converter]
var margin = {top: 20, right: 200, bottom: 100, left: 50},
margin2 = { top: 430, right: 10, bottom: 20, left: 40 },
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
height2 = 500 - margin2.top - margin2.bottom;
var parseDate = d3.time.format("%Y%m%d").parse;
var bisectDate = d3.bisector(function(d) { return d.date; }).left;
var xScale = d3.time.scale()
.range([0, width]),
xScale2 = d3.time.scale()
.range([0, width]); // Duplicate xScale for brushing ref later
var yScale = d3.scale.linear()
.range([height, 0]);
// 40 Custom DDV colors
var color = d3.scale.ordinal().range(["#48A36D", "#56AE7C", "#64B98C", "#72C39B", "#80CEAA", "#80CCB3", "#7FC9BD", "#7FC7C6", "#7EC4CF", "#7FBBCF", "#7FB1CF", "#80A8CE", "#809ECE", "#8897CE", "#8F90CD", "#9788CD", "#9E81CC", "#AA81C5", "#B681BE", "#C280B7", "#CE80B0", "#D3779F", "#D76D8F", "#DC647E", "#E05A6D", "#E16167", "#E26962", "#E2705C", "#E37756", "#E38457", "#E39158", "#E29D58", "#E2AA59", "#E0B15B", "#DFB95C", "#DDC05E", "#DBC75F", "#E3CF6D", "#EAD67C", "#F2DE8A"]);
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom"),
xAxis2 = d3.svg.axis() // xAxis for brush slider
.scale(xScale2)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left");
var line = d3.svg.line()
.interpolate("basis")
.x(function(d) { return xScale(d.date); })
.y(function(d) { return yScale(d.rating); })
.defined(function(d) { return d.rating; }); // Hiding line value defaults of 0 for missing data
var maxY; // Defined later to update yAxis
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom) //height + margin.top + margin.bottom
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Create invisible rect for mouse tracking
svg.append("rect")
.attr("width", width)
.attr("height", height)
.attr("x", 0)
.attr("y", 0)
.attr("id", "mouse-tracker")
.style("fill", "white");
//for slider part-----------------------------------------------------------------------------------
var context = svg.append("g") // Brushing context box container
.attr("transform", "translate(" + 0 + "," + 410 + ")")
.attr("class", "context");
//append clip path for lines plotted, hiding those part out of bounds
svg.append("defs")
.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
//end slider part-----------------------------------------------------------------------------------
// d3.tsv("data.tsv", function(error, data)
function render(data){
color.domain(d3.keys(data[0]).filter(function(key) { // Set the domain of the color ordinal scale to be all the csv headers except "date", matching a color to an issue
return key !== "date";
}));
data.forEach(function(d) { // Make every date in the csv data a javascript date object format
d.date = parseDate(d.date);
});
var categories = color.domain().map(function(name) { // Nest the data into an array of objects with new keys
return {
name: name, // "name": the csv headers except date
values: data.map(function(d) { // "values": which has an array of the dates and ratings
return {
date: d.date,
rating: +(d[name]),
};
}),
visible: (name === "Unemployment" ? true : false) // "visible": all false except for economy which is true.
};
});
xScale.domain(d3.extent(data, function(d) { return d.date; })); // extent = highest and lowest points, domain is data, range is bouding box
yScale.domain([0, 100
//d3.max(categories, function(c) { return d3.max(c.values, function(v) { return v.rating; }); })
]);
xScale2.domain(xScale.domain()); // Setting a duplicate xdomain for brushing reference later
//for slider part-----------------------------------------------------------------------------------
var brush = d3.svg.brush()//for slider bar at the bottom
.x(xScale2)
.on("brush", brushed);
context.append("g") // Create brushing xAxis
.attr("class", "x axis1")
.attr("transform", "translate(0," + height2 + ")")
.call(xAxis2);
var contextArea = d3.svg.area() // Set attributes for area chart in brushing context graph
.interpolate("monotone")
.x(function(d) { return xScale2(d.date); }) // x is scaled to xScale2
.y0(height2) // Bottom line begins at height2 (area chart not inverted)
.y1(0); // Top line of area, 0 (area chart not inverted)
//plot the rect as the bar at the bottom
context.append("path") // Path is created using svg.area details
.attr("class", "area")
.attr("d", contextArea(categories[0].values)) // pass first categories data .values to area path generator
.attr("fill", "#F1F1F2");
//append the brush for the selection of subsection
context.append("g")
.attr("class", "x brush")
.call(brush)
.selectAll("rect")
.attr("height", height2) // Make brush rects same height
.attr("fill", "#E6E7E8");
//end slider part-----------------------------------------------------------------------------------
// draw line graph
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("x", -10)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Issues Rating");
var issue = svg.selectAll(".issue")
.data(categories) // Select nested data and append to new svg group elements
.enter().append("g")
.attr("class", "issue");
issue.append("path")
.attr("class", "line")
.style("pointer-events", "none") // Stop line interferring with cursor
.attr("id", function(d) {
return "line-" + d.name.replace(" ", "").replace("/", ""); // Give line id of line-(insert issue name, with any spaces replaced with no spaces)
})
.attr("d", function(d) {
return d.visible ? line(d.values) : null; // If array key "visible" = true then draw line, if not then don't
})
.attr("clip-path", "url(#clip)")//use clip path to make irrelevant part invisible
.style("stroke", function(d) { return color(d.name); });
// draw legend
var legendSpace = 450 / categories.length; // 450/number of issues (ex. 40)
issue.append("rect")
.attr("width", 10)
.attr("height", 10)
.attr("x", width + (margin.right/3) - 15)
.attr("y", function (d, i) { return (legendSpace)+i*(legendSpace) - 8; }) // spacing
.attr("fill",function(d) {
return d.visible ? color(d.name) : "#F1F1F2"; // If array key "visible" = true then color rect, if not then make it grey
})
.attr("class", "legend-box")
.on("click", function(d){ // On click make d.visible
d.visible = !d.visible; // If array key for this data selection is "visible" = true then make it false, if false then make it true
maxY = findMaxY(categories); // Find max Y rating value categories data with "visible"; true
yScale.domain([0,maxY]); // Redefine yAxis domain based on highest y value of categories data with "visible"; true
svg.select(".y.axis")
.transition()
.call(yAxis);
issue.select("path")
.transition()
.attr("d", function(d){
return d.visible ? line(d.values) : null; // If d.visible is true then draw line for this d selection
})
issue.select("rect")
.transition()
.attr("fill", function(d) {
return d.visible ? color(d.name) : "#F1F1F2";
});
})
.on("mouseover", function(d){
d3.select(this)
.transition()
.attr("fill", function(d) { return color(d.name); });
d3.select("#line-" + d.name.replace(" ", "").replace("/", ""))
.transition()
.style("stroke-width", 2.5);
})
.on("mouseout", function(d){
d3.select(this)
.transition()
.attr("fill", function(d) {
return d.visible ? color(d.name) : "#F1F1F2";});
d3.select("#line-" + d.name.replace(" ", "").replace("/", ""))
.transition()
.style("stroke-width", 1.5);
})
issue.append("text")
.attr("x", width + (margin.right/3))
.attr("y", function (d, i) { return (legendSpace)+i*(legendSpace); }) // (return (11.25/2 =) 5.625) + i * (5.625)
.text(function(d) { return d.name; });
// Hover line
var hoverLineGroup = svg.append("g")
.attr("class", "hover-line");
var hoverLine = hoverLineGroup // Create line with basic attributes
.append("line")
.attr("id", "hover-line")
.attr("x1", 10).attr("x2", 10)
.attr("y1", 0).attr("y2", height + 10)
.style("pointer-events", "none") // Stop line interferring with cursor
.style("opacity", 1e-6); // Set opacity to zero
var hoverDate = hoverLineGroup
.append('text')
.attr("class", "hover-text")
.attr("y", height - (height-40)) // hover date text position
.attr("x", width - 150) // hover date text position
.style("fill", "#E6E7E8");
var columnNames = d3.keys(data[0]) //grab the key values from your first data row
//these are the same as your column names
.slice(1); //remove the first column name (`date`);
var focus = issue.select("g") // create group elements to house tooltip text
.data(columnNames) // bind each column name date to each g element
.enter().append("g") //create one <g> for each columnName
.attr("class", "focus");
focus.append("text") // http://stackoverflow.com/questions/22064083/d3-js-multi-series-chart-with-y-value-tracking
.attr("class", "tooltip")
.attr("x", width + 20) // position tooltips
.attr("y", function (d, i) { return (legendSpace)+i*(legendSpace); }); // (return (11.25/2 =) 5.625) + i * (5.625) // position tooltips
// Add mouseover events for hover line.
d3.select("#mouse-tracker") // select chart plot background rect #mouse-tracker
.on("mousemove", mousemove) // on mousemove activate mousemove function defined below
.on("mouseout", function() {
hoverDate
.text(null) // on mouseout remove text for hover date
d3.select("#hover-line")
.style("opacity", 1e-6); // On mouse out making line invisible
});
function mousemove() {
var mouse_x = d3.mouse(this)[0]; // Finding mouse x position on rect
var graph_x = xScale.invert(mouse_x); //
//var mouse_y = d3.mouse(this)[1]; // Finding mouse y position on rect
//var graph_y = yScale.invert(mouse_y);
//console.log(graph_x);
var format = d3.time.format('%b %Y'); // Format hover date text to show three letter month and full year
hoverDate.text(format(graph_x)); // scale mouse position to xScale date and format it to show month and year
d3.select("#hover-line") // select hover-line and changing attributes to mouse position
.attr("x1", mouse_x)
.attr("x2", mouse_x)
.style("opacity", 1); // Making line visible
// Legend tooltips // http://www.d3noob.org/2014/07/my-favourite-tooltip-method-for-line.html
var x0 = xScale.invert(d3.mouse(this)[0]), /* d3.mouse(this)[0] returns the x position on the screen of the mouse. xScale.invert function is reversing the process that we use to map the domain (date) to range (position on screen). So it takes the position on the screen and converts it into an equivalent date! */
i = bisectDate(data, x0, 1), // use our bisectDate function that we declared earlier to find the index of our data array that is close to the mouse cursor
/*It takes our data array and the date corresponding to the position of or mouse cursor and returns the index number of the data array which has a date that is higher than the cursor position.*/
d0 = data[i - 1],
d1 = data[i],
/*d0 is the combination of date and rating that is in the data array at the index to the left of the cursor and d1 is the combination of date and close that is in the data array at the index to the right of the cursor. In other words we now have two variables that know the value and date above and below the date that corresponds to the position of the cursor.*/
d = x0 - d0.date > d1.date - x0 ? d1 : d0;
/*The final line in this segment declares a new array d that is represents the date and close combination that is closest to the cursor. It is using the magic JavaScript short hand for an if statement that is essentially saying if the distance between the mouse cursor and the date and close combination on the left is greater than the distance between the mouse cursor and the date and close combination on the right then d is an array of the date and close on the right of the cursor (d1). Otherwise d is an array of the date and close on the left of the cursor (d0).*/
//d is now the data row for the date closest to the mouse position
focus.select("text").text(function(columnName){
//because you didn't explictly set any data on the <text>
//elements, each one inherits the data from the focus <g>
return (d[columnName]);
});
};
//for brusher of the slider bar at the bottom
function brushed() {
xScale.domain(brush.empty() ? xScale2.domain() : brush.extent()); // If brush is empty then reset the Xscale domain to default, if not then make it the brush extent
svg.select(".x.axis") // replot xAxis with transition when brush used
.transition()
.call(xAxis);
maxY = findMaxY(categories); // Find max Y rating value categories data with "visible"; true
yScale.domain([0,maxY]); // Redefine yAxis domain based on highest y value of categories data with "visible"; true
svg.select(".y.axis") // Redraw yAxis
.transition()
.call(yAxis);
issue.select("path") // Redraw lines based on brush xAxis scale and domain
.transition()
.attr("d", function(d){
return d.visible ? line(d.values) : null; // If d.visible is true then draw line for this d selection
});
};
}; // End Data callback function
function findMaxY(data){ // Define function "findMaxY"
var maxYValues = data.map(function(d) {
if (d.visible){
return d3.max(d.values, function(value) { // Return max rating value
return value.rating; })
}
});
return d3.max(maxYValues);
}
render(dataset);
date should be a string. Thanks to:
https://github.com/d3/d3/issues/2543