I have a scatter plot with D3 and I'm trying to add circles to a selection based on changing data. I'm passing the data selection down to two functions: render and update. The render function is the initial render and update has the enter() and exit() methods. I can easily add the initial data set and get the circles no longer in the data set to exit. I'm using d.id as the d3 placeholder.
The problem: when I try to enter() the added data points, nothing happens. I've checked the length of the new data selection, and it's larger than the pre-existing. On the DOM, the smaller data set remains (the circles that were already there), but no new circles enter, even though the data set has changed.
I've looked through lots of tutorials regarding data joins, and I think I've appropriately called my enter() and exit() methods. What gives?
Here is my code:
var container = angular.element(document.querySelector('.chart-container'))[0];
var margin = {
top: container.clientHeight / 12,
right: container.clientWidth / 14,
bottom: container.clientHeight / 10,
left: container.clientWidth / 11
};
var w = container.clientWidth - margin.left - margin.right;
var h = container.clientHeight - margin.top - margin.bottom;
// ******** **************** ******** //
// ******** INITIAL RENDER ******** //
function render(input) {
console.log(Object.keys(input).length);
var xScale = d3.scale.linear()
.domain([0, d3.max(input, function(d) { return d["ctc"]; })])
.range([0, w])
.nice();
var yScale = d3.scale.linear()
.domain([0, d3.max(input, function(d) { return d["ttc"]; })])
.range([h, 0])
.nice();
var rScale = d3.scale.linear()
.domain([0, d3.max(input, function(d) { return d["effective"]; })])
.range([2, 15]);
// *********** //
// SVG ELEMENT //
var svg = d3.select('.chart-container')
.append('svg')
.attr('class', 'scatter')
.attr('viewBox', '0, 0, ' + Math.round(w + margin.left + margin.right) + ', ' + Math.round(h + margin.top + margin.bottom))
.attr('preserveAspectRatio', 'xMinYMin')
// used to center element and make use of margins
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
// add circles in group
var circles = svg.append('g')
.attr('class','circles')
.attr('clip-path','url(#chart-area)');
// add individual circles
var circle = circles.selectAll('circle')
.data(input, function(d) {return d.id;})
.enter()
.append('circle')
.attr('class', 'circle')
.attr('cx', function(d) { return xScale(d["ctc"]); })
.attr('cy', function(d) { return yScale(d["ttc"]); })
.attr('r', function(d) { return rScale(d["effective"]); })
.attr('fill', function(d, i) { return d["effective"]; })
.on('mouseover', function(d) {
tooltip.style('visibility', 'visible');
return tooltip.text(d["technology"]);
})
.on("mousemove", function(){ return tooltip.style("top",
(d3.event.pageY-10)+"px").style("left",(d3.event.pageX+10)+"px");})
.on("mouseout", function(){return tooltip.style("visibility", "hidden");})
// append clip path
svg.append('clipPath')
.attr('id','chart-area')
.append('rect')
.attr('class', 'rect')
.attr('x', 0)
.attr('y', 0)
.attr('width', w)
.attr('height', h);
};
// ******** **************** ******** //
// ******** UPDATE ******** //
function update(updateObject) {
var input = updateObject;
var xScale = d3.scale.linear()
.domain([0, d3.max(input, function(d) { return d["ctc"]; })])
.range([0, w])
.nice();
var yScale = d3.scale.linear()
.domain([0, d3.max(input, function(d) { return d["ttc"]; })])
.range([h, 0])
.nice();
var rScale = d3.scale.linear()
.domain([0, d3.max(input, function(d) { return d["effective"]; })])
.range([2, 15]);
var svg = d3.select('svg')
.data(input)
.attr('viewBox', '0, 0, ' + Math.round(w + margin.left + margin.right) + ', ' + Math.round(h + margin.top + margin.bottom))
.attr('preserveAspectRatio', 'xMinYMin')
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
// BIND TO DATA
var circles = d3.selectAll('circle')
.data(input, function(d) { return d.id; });
// Circles Enter
circles.enter()
.insert('svg:circle')
.attr('class', 'circle')
.attr('cx', function(d) { return xScale(d["ctc"]); })
.attr('cy', function(d) { return yScale(d["ttc"]); })
.attr('r', function(d) { return rScale(d["effective"]); });
/*
.on('mouseover', function(d) {
tooltip.style('visibility', 'visible');
return tooltip.text(d["technology"]);
})
.on("mousemove", function(){ return tooltip.style("top",
(d3.event.pageY-10)+"px").style("left",(d3.event.pageX+10)+"px");})
.on("mouseout", function(){return tooltip.style("visibility", "hidden");})
*/
// UPDATE
circles.transition()
.duration(1000)
.attr('cx', function(d) { return xScale(d["ctc"]); })
.attr('cy', function(d) { return yScale(d["ttc"]); })
.attr('r', function(d) { return rScale(d["effective"]); });
// EXIT
circles.exit()
.transition()
.duration(500)
.attr('r', 0)
.style('opacity', 0)
.style('fill', 'gray')
.remove();
}
Update
here is a codepen for testing: http://codepen.io/himmel/pen/JdNJMM
The problem with the code is, that you are trying to use an object as data. d3.selection.data() takes an array, not an object. See the d3 wiki for more information on the data() function.
I have created an updated version of your codepen. I changed the data to an array and applied the correct conventional margin. Moreover I simplified the code by removing the double initialization of scales and the svg element.
Related
Can anyone tell me what I am doing wrong? I am getting an error.
Uncaught (in promise) TypeError: xScale.bandwidth is not a function
at barChart (bar_chart.js:53:27)
at bar_chart.js:84:5
I am trying to create a bar graph of this data.
year,total_ghg
2000,661.97
2001,665.72
2002,660.75
2003,583.65
2004,635.5
2005,598.44
2006,646.91
2007,646.46
2008,617.09
2009,633.8
2010,601.14
2011,644.74
2012,643.12
2013,555.26
2014,566.21
2015,566.47
2016,577.32
2017,623.08
2018,619.26
my js
var dataset;
function barChart(dataset) {
//declaring Varibales
var margin = {top:50, right:50, bottom:50, left:50};
var width = 500-margin.left-margin.right;
var height = 500-margin.top-margin.bottom;
//creating svg
var svg = d3
.select("#barchart")
.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 + ")");
//setting up scales
var xScale = d3
.scaleTime()
.domain([
d3.min(dataset, function (d) {
return d.year;
}),
d3.max(dataset, function (d) {
return d.year;
}),
])
.range([0,width]);
var yScale = d3.scaleLinear()
.domain([0,d3.max(dataset, function (d) {
return d.total_ghg;
})
])
.range([height,0]);
// Plotting axis
svg.append("g").attr("transform", "translate(0," + height + ")").call(d3.axisBottom(xScale));
svg.append("g").call(d3.axisLeft(yScale));
//Set up groups
svg.selectAll("mybar")
.enter()
.data(dataset).append("rect")
.attr("x", function(d) {
return xScale(d.year);
})
.attr('y', function(d) {
return yScale(d.total_ghg);
})
.attr("width", xScale.rangeBand())
.attr('height', function(d) {
return height - yScale(d.total_ghg);
})
.attr("fill", "#004DA5")
.on("mouseover", function(event, d) {
d3.select(this).attr("fill", "orange");
var xPosition = parseFloat(d3.select(this).attr("x")) + xScale.bandwidth() / 2 - 5;
var yPosition = parseFloat(d3.select(this).attr("y")) + 20;
svg.append("text")
.attr("id", "tooltip")
.attr("x", xPosition)
.attr("y", yPosition)
.text(d);
}).on("mouseout", function(d) {
d3.select("#tooltip").remove();
d3.select(this)
.attr("fill", "#004DA5")
});
}
function init() {
d3.csv("src/data/total_ghp.csv", function (d) {
// + : force the year and month to be typed as a number instead of string
return {
year: d3.timeParse("%Y")(d.year),
total_ghg: +d.total_ghg,
};
}).then(function (data) {
dataset = data;
barChart(dataset);
});
}
window.addEventListener("load", init);
Any suggestions Please
What I have tried
ScaleOrdinal
rangeBandRounds
RangeBand()
instead of bandwidth
and a few more things like using a different d3 script same error in every scenario
Your xScale uses scaleTime, which is meant for charts where the axis represents time (i.e. line charts). For bar charts you should use scaleBand instead which does have the bandwidth function:
const xScale = d3
.scaleBand()
.domain([
d3.min(dataset, (d) => d.year),
d3.max(dataset, (d) = > d.year),
])
.range([0, width]);
More information on scaleBand can be found here: https://observablehq.com/#d3/d3-scaleband
There are some other mistakes in your code that prevent your bars from rendering:
Replace scaleTime with scaleBand for xScale
Replace xScale.rangeBand() with xScale.bandwidth()
Move .enter() to come after .data(dataset)
I am newbie in d3js, I do not know why all labels in the bar are wrong.
My code and captures are shown as below, then you can see that all labels are different from my data.
Anyone know what's going on in my text label section?
// set the dimensions and margins of the graph
var margin = { top: 10, right: 30, bottom: 40, left: 50 },
width = 700 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
const dataUrl = "https://raw.githubusercontent.com/yushinglui/IV/main/time_distance_status_v2.csv"
//fetch the data
d3.csv(dataUrl)
.then((data) => {
// append the svg object to the body of the page
var svg = d3.select("#graph-2")
.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 + ")")
// List of subgroups = header of the csv files = soil condition here
var subgroups = data.columns.slice(1)
// List of groups = species here = value of the first column called group -> I show them on the X axis
var groups = d3.map(data, function (d) { return (d.startTime) })
// Add X axis
var x = d3.scaleBand()
.domain(groups)
.range([0, width])
.padding([0.2])
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).tickSize(0));
// Add Y axis
var y = d3.scaleLinear()
.domain([0, 20])
.range([height, 0]);
svg.append("g")
.call(d3.axisLeft(y));
// Another scale for subgroup position?
var xSubgroup = d3.scaleBand()
.domain(subgroups)
.range([0, x.bandwidth()])
.padding([0.05])
// color palette = one color per subgroup
var color = d3.scaleOrdinal()
.domain(subgroups)
.range(['#98abc5', '#8a89a6'])
// Show the bars
svg.append("g")
.selectAll("g")
// Enter in data = loop group per group
.data(data)
.enter()
.append("g")
.attr("transform", function (d) { return "translate(" + x(d.startTime) + ",0)"; })
.selectAll("rect")
.data(function (d) { return subgroups.map(function (key) { return { key: key, value: d[key] }; }); })
.enter()
.append("rect")
.attr("x", function (d) { return xSubgroup(d.key); })
.attr("y", function (d) { return y(d.value); })
.attr("width", xSubgroup.bandwidth())
.attr("height", function (d) { return height - y(d.value); })
.attr("fill", function (d) { return color(d.key); })
// mouseover and mouseout animation
.on("mouseover", function (d) {
d3.select(this).style("fill", d3.rgb(color(d.key)).darker(2))
})
.on("mouseout", function (d) {
d3.select(this).style("fill", function (d) { return color(d.key); })
})
//axis labels
svg.append('text')
.attr('x', - (height / 2))
.attr('y', width - 650)
.attr('transform', 'rotate(-90)')
.attr('text-anchor', 'middle')
.style("font-size", "17px")
.text('Average Distance');
svg.append('text')
.attr('x', 300)
.attr('y', width - 240)
.attr('transform', 'rotate()')
.attr('text-anchor', 'middle')
.style("font-size", "17px")
.text('Start Time');
// legend
svg.append("circle").attr("cx", 200).attr("cy", 20).attr("r", 6).style("fill", "#98abc5")
svg.append("circle").attr("cx", 300).attr("cy", 20).attr("r", 6).style("fill", "#8a89a6")
svg.append("text").attr("x", 220).attr("y", 20).text("Present").style("font-size", "15px").attr("alignment-baseline", "middle")
svg.append("text").attr("x", 320).attr("y", 20).text("Absent").style("font-size", "15px").attr("alignment-baseline", "middle")
//text labels on bars -- all labels wrong!!
svg.append("g")
.selectAll("g")
// Enter in data = loop group per group
.data(data)
.enter()
.append("g")
.attr("transform", function (d) { return "translate(" + x(d.startTime) + ",0)"; })
.selectAll("text")
.data(function (d) { return subgroups.map(function (key) { return { key: key, value: d[key] }; }); })
.enter()
.append("text")
.text(function (d) { return y(d.value); })
.attr("font-family", "sans-serif")
.attr("font-size", "12px")
.attr("fill", "black")
.attr("text-anchor", "middle")
.attr("x", function (d) { return xSubgroup(d.key); })
.attr("y", function (d) { return y(d.value) + 10; })
});
My reference website:
http://plnkr.co/edit/9lAiAXwet1bCOYL58lWN?p=preview&preview
https://bl.ocks.org/bricedev/0d95074b6d83a77dc3ad
Your issue is that when you're appending the text, you inadvertently called the y function, which is used to get the y-location on where to insert the text. The numbers you're getting are actually y-location values, which seems completely random.
.text(function (d) { return y(d.value); }) // here is the issue
Change it to
.text(function (d) { return d.value; })
and it should work!
I'm new to using D3 and JavaScript, so hopefully this is a simple point someone can clear up for me.
I'm making a scatter graph with draggable points, using very similar code to this stack overflow question. When I make a dataset as an array of [x, y] pairs and refer to them as d[0] and d[1] then the code works as intended. However, when I make an array of objects with attributes x and y, referring to them as d.x and d.y, the plot appears as before but the dragging behaviour doesn't work- the point that is clicked shoots down below the x axis.
So this code works:
<!DOCTYPE html>
<svg width="500" height="350"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
let svg = d3.select("svg"),
margin = {top: 20, right: 20, bottom: 30, left: 50},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
//Make the fake data as an array of [x, y] pairs
let m=3.0, c=15.0;
let points = d3.range(1, 10).map(function(i) {
let x=i * width / 10;
let noise=Math.random()*500;
let y=m*x+c + noise;
return [x, y];
});
let x = d3.scaleLinear()
.rangeRound([0, width]);
let y = d3.scaleLinear()
.rangeRound([height, 0]);
let xAxis = d3.axisBottom(x),
yAxis = d3.axisLeft(y);
let line = d3.line()
.x(function(d) { return x(d[0]); })
.y(function(d) { return y(d[1]); });
let drag = d3.drag()
.on('start', dragstarted)
.on('drag', dragged)
.on('end', dragended);
svg.append('rect')
.attr('class', 'zoom')
.attr('cursor', 'move')
.attr('fill', 'none')
.attr('pointer-events', 'all')
.attr('width', width)
.attr('height', height)
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
let focus = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
x.domain(d3.extent(points, function(d) { return d[0]; }));
y.domain(d3.extent(points, function(d) { return d[1]; }));
focus.append("path")
.datum(points)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("stroke-width", 1.5)
.attr("d", line);
focus.selectAll('circle')
.data(points)
.enter()
.append('circle')
.attr('r', 5.0)
.attr('cx', function(d) { return x(d[0]); })
.attr('cy', function(d) { return y(d[1]); })
.style('cursor', 'pointer')
.style('fill', 'steelblue');
focus.selectAll('circle')
.call(drag);
focus.append('g')
.attr('class', 'axis axis--x')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis);
focus.append('g')
.attr('class', 'axis axis--y')
.call(yAxis);
function dragstarted(d) {
d3.select(this).raise().classed('active', true);
}
function dragged(d) {
d[0] = x.invert(d3.event.x);
d[1] = y.invert(d3.event.y);
d3.select(this)
.attr('cx', x(d[0]))
.attr('cy', y(d[1]))
focus.select('path').attr('d', line);
}
function dragended(d) {
d3.select(this).classed('active', false);
}
</script>
But this code gives weird behaviour when dragging the points:
<!DOCTYPE html>
<svg width="500" height="350"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
let svg = d3.select("svg"),
margin = {top: 20, right: 20, bottom: 30, left: 50},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
//Make the fake data as an array of objects, with attributes x and y
let m=3.0, c=15.0;
let points = d3.range(1, 10).map(function(i) {
let x_val=i * width / 10;
let noise=Math.random()*500;
let y_val=m*x_val+c + noise;
return {
x:x_val,
y:y_val
};
});
let x = d3.scaleLinear()
.rangeRound([0, width]);
let y = d3.scaleLinear()
.rangeRound([height, 0]);
let xAxis = d3.axisBottom(x),
yAxis = d3.axisLeft(y);
let line = d3.line()
.x(function(d) { return x(d.x); })
.y(function(d) { return y(d.y); });
let drag = d3.drag()
.on('start', dragstarted)
.on('drag', dragged)
.on('end', dragended);
svg.append('rect')
.attr('class', 'zoom')
.attr('cursor', 'move')
.attr('fill', 'none')
.attr('pointer-events', 'all')
.attr('width', width)
.attr('height', height)
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
let focus = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
x.domain(d3.extent(points, function(d) { return d.x; }));
y.domain(d3.extent(points, function(d) { return d.y; }));
focus.append("path")
.datum(points)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("stroke-width", 1.5)
.attr("d", line);
focus.selectAll('circle')
.data(points)
.enter()
.append('circle')
.attr('r', 5.0)
.attr('cx', function(d) { return x(d.x); })
.attr('cy', function(d) { return y(d.y); })
.style('cursor', 'pointer')
.style('fill', 'steelblue');
focus.selectAll('circle')
.call(drag);
focus.append('g')
.attr('class', 'axis axis--x')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis);
focus.append('g')
.attr('class', 'axis axis--y')
.call(yAxis);
function dragstarted(d) {
d3.select(this).raise().classed('active', true);
}
function dragged(d) {
d.x = x.invert(d3.event.x);
d.y = y.invert(d3.event.y);
d3.select(this)
.attr('cx', x(d.x))
.attr('cy', y(d.y));
focus.select('path').attr('d', line);
}
function dragended(d) {
d3.select(this).classed('active', false);
}
</script>
Could anyone explain why they give different results, when all I've changed is the structure of the data? Thanks!
The x and y attributes on the data are confusing the default (subject accessor)[https://github.com/d3/d3-drag#drag_subject]. Explicitly it will fix the problem:
let drag = d3.drag()
.on('start', dragstarted)
.on('drag', dragged)
.subject(function(d){ return {x: x(d.x), y: y(d.y)} })
.on('end', dragended);
I'm a little new to using D3, and trying to create a scatter-plot. I seem to have lots of repeating code due for window resize, and rendering on data updates. I'm not sure if it's the nature of D3 and updating data or if I'm overlooking a pretty obvious update pattern.
The directive seems lengthy, but only because I have similar code repeated in updateWindow() (for browser resize), render (for the initial rendering and inserting SVG elements, and update (for updating the element attributes when the data changes). Here is my directive, currently:
app.directive('scatterPlot', function ($window, inputService, dataHandler) {
return {
restrict: 'E',
scope: {
input: '=',
display: '='
},
link: function (scope, element, array) {
var container = angular.element(document.querySelector('section.output'))[0];
var filterCount = 0;
var data = undefined;
// ********** ********** ************ //
// ********* ************ *********** //
// ******** ************** ********** //
// ******* **************** ********* //
// ******* WATCH AND RENDER ********* //
scope.$on('inputChange', function(event, isDeleted, isEmpty, isComplete, id, value) {
// CHECK FOR MATCHES //
console.log('event: ', event);
console.log('isDeleted: ', isDeleted, ', isEmpty: ', isEmpty, ', isComplete: ', isComplete, ', id: ', id, ', value: ', value);
if (isDeleted) {
filterCount -= 1;
console.log('filterCount: ', filterCount);
} else {
if (filterCount == 0) {
dataHandler.matchQuery(id, value).then(function(matches) {
data = matches;
filterCount += 1;
return render(matches);
});
} else {
dataHandler.matchQuery(id, value, data).then(function(matches) {
data = matches;
filterCount += 1;
if (filterCount > 0) {
return update(data);
}
});
}
}
});
var formatAsMillions = d3.format('$' + "s");
var formatAsYears = function(d) { return Math.floor(d/365) + ' Years'; };
function update(data) {
var el = element[0];
var margin = {
top: container.clientHeight / 12,
right: container.clientWidth / 7,
bottom: container.clientHeight / 5,
left: container.clientWidth / 11
};
var w = (container.clientWidth - margin.left - margin.right) * 0.9;
var h = (container.clientHeight - margin.top - margin.bottom) * 0.9;
var input = data;
var xScale = d3.scale.linear()
.domain([0, d3.max(input, function(d) { return d["ctc"]; })])
.range([0, w])
.nice();
var yScale = d3.scale.linear()
.domain([0, d3.max(input, function(d) { return d["ttc"]; })])
.range([h, 0])
.nice();
var rScale = d3.scale.linear()
.domain([0, d3.max(input, function(d) { return d["effective"]; })])
.range([2, 10]);
var xAxis = d3.svg.axis()
.scale(xScale)
.orient('bottom')
.ticks(5)
.tickFormat(formatAsMillions);
var max= xScale.domain()[1];
var yAxis = d3.svg.axis()
.scale(yScale)
.orient('left')
.ticks(8)
.tickFormat(formatAsYears)
.tickFormat(function (days) {
if (max < 50) {
return d3.format("2.1f")(days) + " d";
}
else if (max >= 50 && max < 100) {
return d3.format("2.1f")(days/7) + " w";
}
else if (max >= 100 && max < 500) {
return d3.format("2.1f")(days/(365/12)) + " m";
}
else if (max >= 500){
return d3.format("2.1f")(days/365) + " y";
}
});
// *********** //
// SVG ELEMENT //
var svg = d3.select('svg')
.attr('class', 'scatter')
.attr('width', w + margin.left + margin.right)
.attr('height', h + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
// add X axis
d3.select('.xAxis')
.data(input)
.remove()
.exit()
svg.append('g')
.attr('class', 'xAxis')
.attr('transform', 'translate(0,' + h + ')')
.call(xAxis);
// add Y axis
d3.select('.yAxis')
.data(input)
.remove()
.exit()
svg.append('g')
.attr('class', 'yAxis')
.call(yAxis);
// DATA JOIN
// Join new data with old elements, if any.
var circle = d3.selectAll('circle')
.data(input, function(d) {return d.id;})
.remove()
.exit();
// UPDATE
// Update old elements as needed.
circle.attr('class', 'update')
.transition()
.duration(300)
.attr('cx', function(d) { return xScale(d["ctc"]); })
.attr('cy', function(d) { return yScale(d["ttc"]); })
.attr('r', function(d) { return rScale(d["effective"]); });
}
function render(matches) {
console.log(matches.length);
// ********** ********** ************ //
// ********* ************ *********** //
// ******** ************** ********** //
// ******** **************** ******** //
// ******** BASIC ATTRIBUTES ******** //
var input = matches;
var el = element[0];
var effectiveScale = d3.scale.linear()
.domain([1, d3.max(input, function(d) {return d["effective"]; }) ])
.range(["#35ca98", "#029765", "#006432", "#001700"]);
var margin = {
top: container.clientHeight / 12,
right: container.clientWidth / 7,
bottom: container.clientHeight / 5,
left: container.clientWidth / 11
};
var w = (container.clientWidth - margin.left - margin.right) * 0.9;
var h = (container.clientHeight - margin.top - margin.bottom) * 0.9;
var xScale = d3.scale.linear()
.domain([0, d3.max(input, function(d) { return d["ctc"]; })])
.range([0, w])
.nice();
var yScale = d3.scale.linear()
.domain([0, d3.max(input, function(d) { return d["ttc"]; })])
.range([h, 0])
.nice();
var rScale = d3.scale.linear()
.domain([0, d3.max(input, function(d) { return d["effective"]; })])
.range([2, 15]);
var xAxis = d3.svg.axis()
.scale(xScale)
.orient('bottom')
.ticks(5)
.tickFormat(formatAsMillions);
var yAxis = d3.svg.axis()
.scale(yScale)
.orient('left')
.ticks(8)
.tickFormat(formatAsYears);
// *********** //
// SVG ELEMENT //
var svg = d3.select(el)
.append('svg')
.attr('class', 'scatter')
.attr('width', w + margin.left + margin.right)
.attr('height', h + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
// add X axis
svg.append('g')
.attr('class', 'xAxis')
.attr('transform', 'translate(0,' + h + ')')
.call(xAxis);
// add Y axis
svg.append('g')
.attr('class', 'yAxis')
.call(yAxis);
//Create the tooltip label
var tooltip = d3.select('body')
.append('div')
.attr('class', 'tooltip')
.style('position', 'absolute')
.style('z-index', '10')
.style('visibility', 'hidden')
// add circles in group
var circles = svg.append('g')
.attr('class','circles')
.attr('clip-path','url(#chart-area)');
// add individual circles
var circle = circles.selectAll('circle')
.data(input, function(d) {return d.id;})
.enter()
.append('circle')
.attr('class', 'circle')
.attr('cx', function(d) { return xScale(d["ctc"]); })
.attr('cy', function(d) { return yScale(d["ttc"]); })
.attr('r', function(d) { return rScale(d["effective"]); })
.attr('fill', function(d, i) { return effectiveScale(d["effective"])})
.on('mouseover', function(d) {
tooltip.style('visibility', 'visible');
return tooltip.text(d["technology"]);
})
.on("mousemove", function(){ return tooltip.style("top",
(d3.event.pageY-10)+"px").style("left",(d3.event.pageX+10)+"px");})
.on("mouseout", function(){return tooltip.style("visibility", "hidden");})
// NEW SCOPE VALUE ON CLICK //
.on('click', function(d) {
scope.display = d;
console.log(d);
scope.$apply();
return d;
});
// append clip path
svg.append('clipPath')
.attr('id','chart-area')
.append('rect')
.attr('class', 'rect')
.attr('x', 0)
.attr('y', 0)
.attr('width', w)
.attr('height', h);
// add text
svg.selectAll('text')
.data(input, function(d) {return d.id;})
.enter()
.append('text')
.attr('class', 'label')
.attr('clip-path','url(#chart-area)')
.attr('x', function(d) { return xScale(d["ctc"]); })
.attr('y', function(d) { return yScale(d["ttc"]) - rScale(d["effective"]) - 9; })
//.text(function(d) { return d["technology"]; });
// ********** ********** ************ //
// ********* ************ *********** //
// ******** ************** ********** //
// ******** ************** ********** //
// ******** UPDATE WINDOW *********** //
angular.element($window).bind('resize', function() {
updateWindow();
});
function updateWindow() {
var w = container.clientWidth - margin.left - margin.right;
var h = (container.clientHeight - margin.top - margin.bottom) * 0.9;
var xScale = d3.scale.linear()
.domain([0, d3.max(input, function(d) { return d["ctc"]; })])
.range([0, w])
.nice();
var yScale = d3.scale.linear()
.domain([0, d3.max(input, function(d) { return d["ttc"]; })])
.range([h, 0])
.nice();
var xAxis = d3.svg.axis()
.scale(xScale)
.orient('bottom')
.ticks(5)
.tickFormat(formatAsMillions);
var yAxis = d3.svg.axis()
.scale(yScale)
.orient('left')
.ticks(8)
.tickFormat(formatAsYears);
// add X axis
d3.select('.xAxis')
.attr('transform', 'translate(0,' + h + ')')
.call(xAxis);
// add Y axis
d3.select('.yAxis')
.call(yAxis);
/// adjust svg element width/height
d3.select('.scatter')
.attr('width', w + margin.left + margin.right)
.attr('height', h + margin.top + margin.bottom)
.select('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
.attr('width', w + margin.left + margin.right)
.attr('height', h + margin.top + margin.bottom);
// adjust circle location
d3.select('.circles')
.attr('clip-path','url(#chart-area)')
.selectAll('circle')
.attr('cx', function(d) { return xScale(d["ctc"]); })
.attr('cy', function(d) { return yScale(d["ttc"]); })
.attr('r', function(d) { return rScale(d["effective"]); });
// adjust clip path
d3.select('.rect')
.attr('x', 0)
.attr('y', 0)
.attr('width', w)
.attr('height', h);
}
};
}
};
});
Is there a common update pattern for reusing some of the same declarations here?
I am currently modifying the following example to produce a multi horizontal relative stack chart. The following example is a "single" stack chart example.
http://jsfiddle.net/zDkWP/
Here is my version however I get NaN X and Width values and I'm looking to find out why. Thank you.
Within the SVG the structure is as follows for each of the : -
g[ g [rect, rect, rect]], g[ g [rect, rect, rect]]
Here is my code: http://jsfiddle.net/scottietom/7c3vb4e9/
var dataset = [
[
[{x: 0,y: 100}],[{x: 0,y: 30}],[{x: 0,y: 50}]],
[[{x: 0,y: 100}],[{x: 0,y: 30}],[{x: 0,y: 50}]]
];
//Set up stack method
var stack = d3.layout.stack();
//Data, stacked
for (i=0; i<dataset.length; i++) {
stack(dataset[i]);
}
//Set up scales
var xScale = d3.scale.linear()
.domain([0, d3.max(dataset, function (d) {
return d3.max(d, function (d) {
return d.y0 + d.y;
});
})])
.range([0, w]);
//Easy colors accessible via a 10-step ordinal scale
var colors = d3.scale.category10();
//or make your own colour palet
var color = d3.scale.ordinal()
.range(["#1459D9", "#148DD9", "#87ceeb", "#daa520"]);
//Create SVG element
var svg = d3.select(".pathanalysis_diagram")
.append("svg")
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Add a group for each row of data
var groups = svg.selectAll("g")
.data(dataset)
.enter()
.append("g")
.style("fill", function (d, i) {
return color(i);
});
// Add a rect for each data value
var rects = groups.selectAll("rect")
.data(function (d) {
return d;
})
.enter()
.append("rect")
.attr("x", function (d) {
return xScale(d.y0);
})
.attr("y", 50)
.attr("height", 50)
.attr("width", function (d) {
return xScale(d.y);
});
Here's a JSFiddle with your solution: http://jsfiddle.net/ee2todev/z73u6mro/
I called the nested dataset arrayOfDatasets so the changes become more clear.
First you have to call stack() for each of the datasets:
arrayOfDatasets.forEach(function (dataset) {stack(dataset); });
Then you have to retrieve the max for xScale:
var xScale = d3.scale.linear()
.domain([0, d3.max(arrayOfDatasets, function (dataset) {
return d3.max(dataset, function (d) {
return d3.max(d, function (d) {
return d.y0 + d.y;
});
})
})])
.range([0, w]);
Finally, you just have to access the data properly and translate the datasets so they don't overlap:
var groupOfGroups = svg.selectAll("g.toplevel")
.data(arrayOfDatasets)
.enter()
.append("g")
.attr("class", "toplevel")
.attr("transform", function(d, i ) {return "translate(0, " + (-i * 55) + ")"; });
// Add a group for each row of data
var groups = groupOfGroups.selectAll("g.dataset")
.data(function(d) {return d; })
.enter()
.append("g")
.attr("class", "dataset")
.style("fill", function (d, i) {
return color(i);
});
You might still store your datasets differently since it's not clear why you have the x values in them.