D3 Progress Bar Animation Does Not Work In Angular Directive - javascript

I am having trouble getting my progress bars to animate within an AngularJS directive. I am at least able to get it to show the value that I want but I can't make it animate from 0 to an arbitrary progress value. Are there any blatant mistakes in my code that would make the animation fail? I don't understand how the animations work in D3. An additional note, there will be several progress bars on the page I am coding.
myApp.directive('progressItem', function($window){
return{
scope: '=item',
restrict: 'A',
link: function(scope, element){
var d3 = $window.d3;
var width = 120,
height = 120,
twoPi = 2 * Math.PI,
progress = parseFloat(scope.item.completed / scope.item.goals),
total = 100,
formatPercent = d3.format(".0%");
var arc = d3.svg.arc()
.startAngle(0)
.innerRadius(40)
.outerRadius(50)
;
if(scope.item.value != "add") {
var svg = d3.select(element[0]).append("svg")
.attr("width", width)
.attr("height", height)
.attr('fill', '#52AD52')
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var meter = svg.append("g")
.attr("class", "progress-meter");
meter.append("path")
.attr("class", "background")
.attr("d", arc.endAngle(twoPi));
var foreground = meter.append("path")
.attr("class", "foreground")
.attr("d", arc.endAngle(twoPi * progress));
var text = meter.append("text")
.attr("text-anchor", "middle")
.text(formatPercent(0));
var i = d3.interpolate(0, progress);
d3.transition().duration(1000).tween("progress", function () {
return function (t) {
progress = i(t);
foreground.attr("d", arc.endAngle(twoPi * progress));
text.text(formatPercent(progress));
};
});
}
else{
element.append('<img class="add" src="../images/add-user.png" />')
}
}
};
});

Related

D3 seemingly updating wrong SVG element

I have a d3 layout that is intended to produce 3 charts:
Custom item layout w/ transitions
Pie chart for sales by category (with transitions at some point)
Bar chart for top 5 performing items w/ transitions.
1 & 2 work OK but when I add the third chart, I see some strange behavior. The intention is to create a bar chart where the width of each bar is tied to the sales metric for the top N items determined like so:
data = data.filter(function(d){ return d.date === someDate});
var cf = crossfilter(data);
var salesDimension = cf.dimension(function(d){ return d.sales; });
topData = salesDimension.top(5);
The problem is that instead of drawing the bar chart, my code somehow overwrites the position of 5 items in chart 1 and moves them back to the origin. If I change 5 to 10 above then 10 items are overwritten and so on.
I double checked the scope of my variables and even tried changing the names of everything in my drawTopItems() which made no difference. I suspect that I am doing something incorrectly when it comes to selecting the svg element or applying classes to the svg group elements that I want to modify but I can't for the life of me see what. Can anyone tell me what I might be doing wrong?
Here is my issue in a fiddle: https://jsfiddle.net/Sledge/4eggpd5e/12/.
Here is my javascript code:
var item_width = 40, item_height = 60;
var margin = {top: 50, right: 50, bottom: 75, left: 40},
width = 700 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scaleLinear().range([0, width]);
var y = d3.scaleLinear().range([0, height]);
var colorScale = d3.scaleLinear().domain([500,3000]).range(["white","#4169e1"]);
// Pie Chart parameters
var pieWidth = 300, pieHeight = 300;
var outerRadius = Math.min(pieWidth, pieHeight) / 2,
innerRadius = outerRadius * .50;
var pieColor = d3.scaleOrdinal(['#42b9f4','#3791f2','#374ff1','#25b22e','#107222']); // custom color scale
var legendRectSize = 18; // NEW
var legendSpacing = 4; // NEW
// Top Item Parameters
var topItemMargin = {top:25, right:25, bottom: 25, left: 25};
var topItemWidth = 300 - topItemMargin.left - topItemMargin.right,
topItemHeight = 300 - topItemMargin.top - topItemMargin.bottom;
var topItemXScale = d3.scaleLinear().range([0, topItemWidth]);
var barHeight = 20, barSpacing = 5;
/* SVG */
var svgItemLayout = d3.select("#item_layout")
.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 svgPieChart = d3.select("#pie_chart")
.append("svg")
.attr("width", pieWidth)
.attr("height", pieHeight)
.append("g")
.attr("transform", "translate(" + outerRadius + "," + outerRadius + ")") ;
var svgTopItems = d3.select("#top_items")
.append("svg")
.attr("width", topItemWidth)
.attr("height", topItemHeight)
.append("g")
.attr("transform", "translate(" + topItemMargin.left + "," + topItemMargin.top + ")");
/* DRAW FUNCTIONS */
// a single function to draw
function drawItemLayout(data, someDate){
data.forEach(function(d) {
d.x_pos = +d.x_pos;
d.y_pos = +d.y_pos;
d.sales = +d.sales;
});
// pre-filter data
data = data.filter(function(d){ return d.date === someDate});
var x_offset = 5, y_offset = 5;
x.domain(d3.extent(data, function(d) { return d.x_pos; })); // set the x domain
y.domain(d3.extent(data, function(d) { return d.y_pos; })); // set the y domain
// create an update selection with a key function
var g_sel = svgItemLayout.selectAll("g")
.data(data, function(d){
return d.item_name;
});
// get rid of those leaving the update
g_sel.exit().remove();
// our entering g
var g_ent = g_sel.enter()
.append("g");
// add our rects to our g
g_ent.append("rect")
.attr("class", "dot") // wonder if I really need this class?
.attr("width", item_width)
.attr("height", item_height)
.attr("rx", 3)
.attr("ry", 3)
.style("fill", function(d){ return colorScale(d.sales); }) // color factor variable
.style("fill-opacity", 0.5);
// add our text to our g
g_ent.append("text")
.attr("font-size", 10)
.attr("text-anchor", "middle")
.attr("fill", "black")
.attr("dx", item_width/2)
.attr("dy", item_height/2)
.text(function(d){ return d.item_name; });
// UPDATE + ENTER selection
g_sel = g_ent.merge(g_sel);
// move them into position with transition
g_sel
.transition()
.duration(1200)
.delay(function(d, i) { return i *40; })
.attr("transform", function(d){
return "translate(" + (x(d.x_pos) + x_offset) + "," + (y(d.y_pos) + y_offset) + ")";
});
}
function drawPieChart(data, someDate) {
data.forEach(function(d) {
d.x_pos = +d.x_pos;
d.y_pos = +d.y_pos;
d.sales = +d.sales;
});
// pre-filter data
data = data.filter(function(d){ return d.date === someDate});
var cf = crossfilter(data);
var salesDimension = cf.dimension(function(d){ return d.sales; });
var categoryDimension = cf.dimension(function(d){ return d.category; });
var categoryGroup = categoryDimension.group();
function reduceInitial(p, v) {
return {
sales : 0,
count : 0
};
}
function reduceAdd(p, v) {
p.sales = p.sales + v.sales;
p.count = p.count + 1;
return p;
}
function reduceRemove(p, v) {
p.sales = p.sales - v.sales;
p.count = p.count - 1;
return p;
}
categoryAggregated = categoryGroup.reduce(reduceAdd, reduceRemove, reduceInitial).all();
var arc = d3.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
var pie = d3.pie()
.value(function(d) { return d.value.sales; })
.sort(null);
var path = svgPieChart.selectAll('path')
.data(pie(categoryAggregated))
.enter()
.append('path')
.attr('d', arc)
.attr('fill', function(d, i) { return pieColor(i);});
// Add a legend:
var legend = svgPieChart.selectAll('.legend')
.data(pieColor.domain())
.enter()
.append('g')
.attr('class', 'legend')
.attr('transform', function(d, i) {
var height = legendRectSize + legendSpacing;
var offset = height * pieColor.domain().length / 2;
var horz = -3 * legendRectSize;
var vert = i * height - offset;
return 'translate(' + horz + ',' + vert + ')';
});
legend.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', pieColor)
.style('stroke', pieColor);
legend.append('text')
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendRectSize - legendSpacing)
.text(function(d) { return categoryAggregated[d].key; }); // returns text based on data index
}
function drawTopItems (data, someDate) {
data.forEach(function(d) {
d.x_pos = +d.x_pos;
d.y_pos = +d.y_pos;
d.sales = +d.sales;
});
// pre-filter data
data = data.filter(function(d){ return d.date === someDate});
var cf = crossfilter(data);
var salesDimension = cf.dimension(function(d){ return d.sales; });
topData = salesDimension.top(5);
topItemXScale.domain(d3.extent(topData, function(d) { return d.sales; })); // set the x domain
var f_sel = svgTopItems.selectAll("g")
.data(topData,function(d){ return d.item_name; }).enter();
f_sel.exit().remove();
var f_ent = f_sel.enter().append("g");
f_ent.append("rect")
.attr("class", "dot") // wonder if I really need this class?
.attr("width", function(d){ return d.sales })
.attr("height", barHeight)
.style("fill","#351eff") // color factor variable
.style("fill-opacity", 0.75);
// add our text to our g
f_ent.append("text")
.attr("font-size", 10)
.attr("text-anchor", "left")
.attr("fill", "black")
.attr("dx", item_width/2)
.attr("dy", item_height/2)
.text(function(d){ return d.item_name});
// UPDATE + ENTER selection
f_sel = f_ent.merge(f_sel);
f_sel.transition()
.duration(1200)
.delay(function(d, i) { return i *40; })
.attr("transform", function(d, i){
return "translate( 0, "+ i*25 +")" + ")";
});
}
/* MAIN */
var data = d3.csvParse( d3.select("pre#data").text());
drawItemLayout(data, '1-20-2017');
drawPieChart(data, '1-20-2017');
drawTopItems(data, '1-20-2017');
/* UPDATE DATA */
function updateData(date) {
//d3.csv("http://localhost:8080/udacity_test_vis_1/output_fixture_data.csv", function(data) {
var data = d3.csvParse( d3.select("pre#data").text());
drawItemLayout (data, date);
drawPieChart(data, date);
drawTopItems(data, date);
}
/* GET SELECTION */
$("#select_params").change(function(){
var date = $("#select_params :selected").val();
console.log(date);
updateData(date);
})
Just three problems:
You are repeating the enter() function:
var f_sel = svgTopItems.selectAll("g")
.data(topData,function(d){ return d.item_name; }).enter();
//this is an enter selection...
var f_ent = f_sel.enter().append("g");
//and you have enter() again here
So, remove the first enter: f_sel should be just the data-binding selection.
Move your merged selection to before appending the rectangles
Your translate has an extra parenthesis:
return "translate( 0, "+ i*25 +")" + ")";
With that problems corrected, this is your updated fiddle: https://jsfiddle.net/utf5hva2/

D3 adding donut chart within a tooltip

I'm trying to display a donut chart within a tooltip. I thought it'll be simply just adding the function name or creating the chart within .html() but that isn't the case sadly. Can anyone tell me where i'm going wrong?
Here's my code:
tooltip.select('.label').html(donutChart());
function donutChart(){
var dataset = {
hddrives: [20301672448, 9408258048, 2147483648, 21474836480, 35622912,32212254720],
};
var width = 460,
height = 300,
radius = Math.min(width, height) / 2;
var color = d3.scale.ordinal()
.range(["#2DA7E2"]);
var pie = d3.layout.pie()
.sort(null);
var arc = d3.svg.arc()
.innerRadius(radius - 100)
.outerRadius(radius - 70);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var path = svg.selectAll("path")
.data(pie(dataset.hddrives))
.enter().append("path")
.attr("class", "arc")
.attr("fill", function(d, i) { return color(i); })
.attr("d", arc);
svg.append("text")
.attr("dy", ".35em")
.style("text-anchor", "middle")
.attr("class", "inside")
.text(function(d) { return 'Test'; });
}
Your function donutChart appends the <svg> to the body, not inside the tooltip.
A solution can be writing this in your .html():
.html("<h1>My Donut Chart</h1><br><svg class='myDonut'></svg>")
And then call your donutChart after that line, remembering to change your var svg:
var svg = d3.select(".myDonut")
Take care for not repeating the same variable names, even if they are inside a function (separate scope)... it can cause unnecessary confusion.

Trying to update d3.js circles with new data

I've recently began trying to teach myself D3, and I'm to get my head round the enter, update, exit paradigm.
Below I have an example of some progress circles I'm trying to work with;
http://plnkr.co/edit/OoIL8v6FemzjzoloJxtQ?p=preview
Now, as the aim here is to update the circle path without deleting them, I believe I shouldn't be using the exit function? In which case, I was under the impression that I could update my data source inside a new function and then call for the path transition, and I would get my updated value. However, this is not the case.
I was wondering if someone could help me out and show me where I'm going wrong?
var dataset = [{
"vendor-name": "HP",
"overall-score": 45
}, {
"vendor-name": "CQ",
"overall-score": 86
}];
var dataset2 = [{
"vendor-name": "HP",
"overall-score": 22
}, {
"vendor-name": "CQ",
"overall-score": 46
}];
var width = 105,
height = 105,
innerRadius = 85;
var drawArc = d3.svg.arc()
.innerRadius(innerRadius / 2)
.outerRadius(width / 2)
.startAngle(0);
var vis = d3.select("#chart").selectAll("svg")
.data(dataset)
.enter()
.append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
vis.append("circle")
.attr("fill", "#ffffff")
.attr("stroke", "#dfe5e6")
.attr("stroke-width", 1)
.attr('r', width / 2);
vis.append("path")
.attr("fill", "#21addd")
.attr('class', 'arc')
.each(function(d) {
d.endAngle = 0;
})
.attr('d', drawArc)
.transition()
.duration(1200)
.ease('linear')
.call(arcTween);
vis.append('text')
.text(0)
.attr("class", "perc")
.attr("text-anchor", "middle")
.attr('font-size', '36px')
.attr("y", +10)
.transition()
.duration(1200)
.tween(".percentage", function(d) {
var i = d3.interpolate(this.textContent, d['overall-score']),
prec = (d.value + "").split("."),
round = (prec.length > 1) ? Math.pow(10, prec[1].length) : 1;
return function(t) {
this.textContent = Math.round(i(t) * round) / round + "%";
};
});
function updateChart() {
vis = vis.data(dataset2)
vis.selectAll("path")
.transition()
.duration(1200)
.ease('linear')
.call(arcTween);
vis.selectAll('text')
.transition()
.duration(1200)
.tween(".percentage", function(d) {
var i = d3.interpolate(this.textContent, d['overall-score']),
prec = (d.value + "").split("."),
round = (prec.length > 1) ? Math.pow(10, prec[1].length) : 1;
return function(t) {
this.textContent = Math.round(i(t) * round) / round + "%";
};
});
}
function arcTween(transition, newAngle) {
transition.attrTween("d", function(d) {
var interpolate = d3.interpolate(0, 360 * (d['overall-score'] / 100) * Math.PI / 180);
return function(t) {
d.endAngle = interpolate(t)
return drawArc(d);
};
});
}
Any help/advice is much appreciated!
Thanks all
You need to refresh your data through the DOM - svg > g > path :
// SET DATA TO SVG
var svg = d3.selectAll("svg")
.data(selectedDataset)
// SET DATA TO G
var g = svg.selectAll('g')
.data(function(d){return [d];})
// SET DATA TO PATH
var path = g.selectAll('path')
.data(function(d){ return [d]; });
Storing the d3 DOM data bind object for each step you can have control of the enter(), extit(), and transition() elements. Put changing attributes of elements in the transition() function:
// PATH ENTER
path.enter()
.append("path")
.attr("fill", "#21addd")
.attr('class', 'arc')
// PATH TRANSITION
path.transition()
.duration(1200)
.ease('linear')
.attr('d', function(d){ console.log(d);drawArc(d)})
.call(arcTween);
http://plnkr.co/edit/gm2zpDdBdQZ62YHhDbLb?p=preview

Animating angular d3 donut on load

I have created a simple Angular/D3 donut chart.
Plunker: http://plnkr.co/edit/NyH1udkjBj3zymhGaThZ?p=preview
However i want the chart to have an animation on page load. In that, i mean, i would like the filled (blue area) to transition in.
Somethign similar to: http://codepen.io/tpalmer/pen/jqlFG/
HTML:
<div ng-app="app" ng-controller="Ctrl">
<d3-donut radius="radius" percent="percent" text="text"></d3-donut>
div>
JS:
var app = angular.module('app', []);
app.directive('d3Donut', function() {
return {
restrict: 'E',
scope: {
radius: '=',
percent: '=',
text: '=',
},
link: function(scope, element, attrs) {
var radius = scope.radius;
var percent = scope.percent;
var text = scope.text;
var svg = d3.select(element[0])
.append('svg')
.style('width', radius / 2 + 'px')
.style('height', radius / 2 + 'px');
var donutScale = d3.scale.linear().domain([0, 100]).range([0, 2 * Math.PI]);
var color = "#5599aa";
var data = [
[0, 100, "#e2e2e2"],
[0, percent, color]
];
var arc = d3.svg.arc()
.innerRadius(radius / 6)
.outerRadius(radius / 4)
.startAngle(function(d) {
return donutScale(d[0]);
})
.endAngle(function(d) {
return donutScale(d[1]);
});
svg.selectAll("path")
.data(data)
.enter()
.append("path")
.attr("d", arc)
.style("fill", function(d) {
return d[2];
})
.attr("transform", "translate(" + radius / 4 + "," + radius / 4 + ")");
svg.append("text")
.attr("x", radius / 4)
.attr("y", radius / 4)
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.attr("font-size", "12px")
.style("fill", "#333")
.attr("text-anchor", "middle")
.text(text);
}
};
});
function Ctrl($scope) {
$scope.radius = 200;
$scope.percent = 50;
$scope.text = "40%";
}
I have combined your code with the one you cited as an example. Here is a working PLUNK.
Two things are worth noting -
1. using attrTween:
.attrTween("d", function (a) {
var i = d3.interpolate(this._current, a);
var i2 = d3.interpolate(progress, percent)
this._current = i(0);
console.log(this._current);
return function(t) {
text.text( format(i2(t) / 100) );
return arc(i(t));
};
});
2. updating the data:
data = [
[0,100,"#e2e2e2"],
[0,percent,color]
];
I did some other less important changes like a more idiomatic use of the controller snippet, etc., but the above is what matters.
Would the Angular 'run' command help here?
angular.module('app', []).run(myfunction($rootScope))
See 'run' on this page https://docs.angularjs.org/api/ng/type/angular.Module
'run()' will give you one-time only execution of so-and-so a function once AngularJs has bootstrapped.

Implement an SVG mask on a RECT with D3 (in Javascript)?

I'm trying to implement an SVG mask in D3, similar to this very simple jsfiddle example, but I must have lost something in translation. My implementation all takes place in a class that renders a graph. I'm trying to apply the mask to define bounds for the graph, so that when the data exceeds those bounds, the graph is neatly clipped. When I apply the mask, the bars of the graph completely disappear. As far as I can tell the mask in the right place. HELP!
Here is where I define the mask in my init() function:
// Add an SVG element with the desired dimensions and margin.
this.graph = d3.select(this.config.id).append("svg:svg")
.attr("width", this.width + this.m[1] + this.m[3])
.attr("height", this.height + this.m[0] + this.m[2])
.append("svg:g")
.attr("transform", "translate(" + this.m[3] + "," + this.m[0] + ")");
var maskWidth = 640;
var maskHeight = 321;
this.graph.append('svg:defs') <------ I START DEFINING IT HERE !
.call(function (defs) {
// Appending the mask
defs.append('svg:mask')
.attr('id', 'mask')
.attr('width', maskWidth)
.attr('height', maskHeight)
.attr('x', 0)
.attr('y', 0)
.call(function(mask) {
mask.append('svg:rect')
.attr('width', maskWidth)
.attr('height', maskHeight)
.attr('fill', '#ffffff')
});
});
Here is the Method that draws bars on the graph where I attempt to apply the mask (see the last line):
addBars: function (data){
var numberOfBars = Math.floor(this.xMaximum);
var barWidth = this.width/numberOfBars;
// Generate a histogram using twenty uniformly-spaced bins.
var histogramData = d3.layout.histogram()
.bins(this.xScale.ticks(numberOfBars))
(data);
//console.trace('typeof: '+typeof this.xScale);
var xScale = this.xScale;
var yScale = this.yScale;
var height = this.height;
this.bars = this.graph.selectAll("bar")
.data(histogramData, function(d){ return d;})
.enter()
.append("rect")
.attr("class","bar")
.attr("fill","steelblue")
.attr("transform", function(d, i) {
var yOffset = height;
return "translate(" + (i * barWidth - barWidth/2) + ","+yOffset+")";
})
.attr("y", function(d,i) {
var yPosition = yScale(d.length)- height;
return (yScale(d.length)-height);
})
.attr("height", function(d) {
return height - yScale(d.length);
})
.attr("width", barWidth - 1)
.attr('mask', 'url(#mask)'); <---- OVER HERE !!!!
},
Here is a link to the resulting HTML in Chrome Developer Tools (I've highlighted the <defs> and one of the graph bars that should be masked):Chrome Developer Tools Dynamic HTML
As far as I can tell everything looks good. This leads me to believe that the mask is mis-aligned with the bar, causing the bar to be invisible. However, in the developer tools, when I hover over the <rect> element, it shows it as overlaying the graph bars, so it doesn't seem like an alignment issue. Any help would be appreciated.
Lastly, I've made a jsfiddle of the class being used in my application (see the comments for the link.). Below is also the entire class for drawing the graph, just in case it would be helpful to see the code in context:
// HistogramGrapher class - constructor
var HistogramGrapher = function() {
// assign default properties
this.config = {
id: "",
xAxisLabel: "xAxis",
yAxisLabel: "yAxis",
width: 1000,
height: 400,
title: "Title",
mean: 20
};
// define variables
this.m = [40, 80, 40, 80]; // margins
this.width; // width
this.height; // height
this.xAxisLabel;
this.yAxisLabel;
this.graph;
this.bars;
this.lines;
this.xScale;
this.xScaleInvert;
this.xAxis;
this.yScale;
this.yScaleInvert;
this.yAxis;
this.yMaximum = 25;
this.xMaximum = 2 * this.config.mean;
}
// methods for this class
HistogramGrapher.prototype = {
init: function (options) {
// copy properties of `options` to `config`. Will overwrite existing ones.
for(var prop in options) {
if(options.hasOwnProperty(prop)){
this.config[prop] = options[prop];
}
}
// update variables
this.updateWidth(this.config.width);
this.updateHeight(this.config.height);
this.updateXMaximum(this.config.mean);
// X scale will fit all values from datay[] within pixels 0-w
this.xScale = d3.scale.linear()
.domain([0, this.xMaximum])
.range([0, this.width]);
this.xScaleInvert = d3.scale.linear()
.range([0, this.xMaximum])
.domain([0, this.width]);
// Y scale
this.yScale = d3.scale.linear()
.domain([0, this.yMaximum])
.range([this.height,0]);
this.yScaleInvert = d3.scale.linear()
.range([0, this.yMaximum])
.domain([this.height,0]);
// Add an SVG element with the desired dimensions and margin.
this.graph = d3.select(this.config.id).append("svg:svg")
.attr("width", this.width + this.m[1] + this.m[3])
.attr("height", this.height + this.m[0] + this.m[2])
.append("svg:g")
.attr("transform", "translate(" + this.m[3] + "," + this.m[0] + ")");
var maskWidth = 640;
var maskHeight = 321;
this.graph.append('svg:defs')
.call(function (defs) {
// Appending the mask
defs.append('svg:mask')
.attr('id', 'mask')
.attr('width', maskWidth)
.attr('height', maskHeight)
.attr('x', 0)
.attr('y', 0)
.call(function(mask) {
mask.append('svg:rect')
.attr('width', maskWidth)
.attr('height', maskHeight)
.attr('fill', '#ffffff')
});
});
// create xAxis
this.xAxis = d3.svg.axis().scale(this.xScale)
.tickSize(-this.height)
.tickSubdivide(true);
// create yAxis
this.yAxis = d3.svg.axis().scale(this.yScale)
.tickSize(-this.width)
.tickSubdivide(true)
.orient("left");
// Add the x-axis label.
this.graph.append("text")
.attr("class", "x label")
.attr("text-anchor", "end")
.attr("x", this.width)
.attr("y", this.height + 25)
.text(this.config.xAxisLabel);
// Add the y-axis label.
this.graph.append("text")
.attr("class", "y label")
.attr("text-anchor", "end")
.attr("y", -30)
.attr("dy", ".75em")
.attr("transform", "rotate(-90)")
.text(this.config.yAxisLabel);
// add Title
this.graph.append("text")
.attr("x", this.width/2 )
.attr("y", -20 )
.attr("text-anchor", "middle")
.style("font-size", "12px")
.text(this.config.title);
// Add the x-axis.
this.graph.append("svg:g")
.attr("class", "x axis")
.attr("transform", "translate(0," + this.height + ")")
.call(this.xAxis);
// Add the y-axis.
this.graph.append("svg:g")
.attr("class", "y axis")
.call(this.yAxis);
},
updateWidth: function(width){
this.width = width - this.m[1] - this.m[3];
},
updateHeight: function(height){
this.height = height - this.m[0] - this.m[2]; // height
},
updateXMaximum: function(mean){
this.xMaximum = 2.5 * mean;
},
addBars: function (data){
var numberOfBars = Math.floor(this.xMaximum);
var barWidth = this.width/numberOfBars;
// Generate a histogram using twenty uniformly-spaced bins.
var histogramData = d3.layout.histogram()
.bins(this.xScale.ticks(numberOfBars))
(data);
//console.trace('typeof: '+typeof this.xScale);
var xScale = this.xScale;
var yScale = this.yScale;
var height = this.height;
this.bars = this.graph.selectAll("bar")
.data(histogramData, function(d){ return d;})
.enter()
.append("rect")
.attr("class","bar")
.attr("fill","steelblue")
.attr("transform", function(d, i) {
var yOffset = height;
return "translate(" + (i * barWidth - barWidth/2) + ","+yOffset+")";
})
.attr("y", function(d,i) {
var yPosition = yScale(d.length)- height;
return (yScale(d.length)-height);
})
.attr("height", function(d) {
return height - yScale(d.length);
})
.attr("width", barWidth - 1)
.attr('mask', 'url(#mask)');
},
addLine: function (data){ // the data must be in the form " [ {'x':x1, 'y':y1} , {'x':x2, 'y':y2} , {'x':x3, 'y':y3} ... ]
var xScale = this.xScale;
var yScale = this.yScale;
var height = this.height;
// create a line function that can convert data[] into x and y points
var lineFunction = d3.svg.line()
// assign the X function to plot our line as we wish
.x(function(d) { return xScale(d.x); })
.y(function(d) { return yScale(d.y); })
.interpolate("linear");
this.lines = this.graph.append("path")
.attr("d", lineFunction(data))
.attr("class", "line")
.attr("stroke", "green")
.attr("stroke-width", 2)
.attr("fill","none");
},
clear: function () {
var bars = d3.selectAll(".bar").remove();
var lines = d3.selectAll(".line").remove();
},
getxScale: function () {
return this.xScale;
},
getxScaleInvert: function () {
return this.xScaleInvert;
}
}
Ok, I saw what's going on. You should apply the clipping mask to the bars and the line by appending a clipping mask to the graph area:
//clipping mask
yourSvg.append("clipPath")
.attr("id", "chart-area")
.append("rect")
.attr("x", yourXcoordinates)
.attr("y", yourYcoordinates)
.attr("width", 333) //this was the width provided by the webinspector
.attr("height", 649) //this was the height provided by the webinspector;
then when you plot the line and the bars, add this to both of the generators
.attr("clip-path", "url(#chart-area)")
and this should give you the clipping you're looking for. Basically what it does is clip everything outside the area of that rectangle, so if you plot it correctly, it should clip out unwanted things

Categories

Resources