I'm building a sort of timeline tool using d3.js that can be zoomed and paged forward and back within a set time period. I have my axis transitioning perfectly but I can't seem to get my data to reload. The idea is that the zoom level defines a moving window, for example 1 hour that the user moves backwards and forwards over a period of a few days. Every time they do so a call is made to fetch the data for that hour.
Apologies for the large code block, the methods that handle the fetching of new data are towards the bottom. If it makes any difference the tool is a directive for angular.js.
scope.zoomLevels = [
{
value: 1,
unit: 'minutes',
tickFormat: $window.d3.time.format('%S')
},
{
value: 30,
unit: 'minutes',
tickFormat: $window.d3.time.format('%H:%M')
},
{
value: 1,
unit: 'hours',
tickFormat: $window.d3.time.format('%H:%M')
},
{
value: 3,
unit: 'hours',
tickFormat: $window.d3.time.format('%H:%M')
},
{
value: 6,
unit: 'hours',
tickFormat: $window.d3.time.format('%H:%M')
},
{
value: 12,
unit: 'hours',
tickFormat: $window.d3.time.format('%H:%M')
},
{
value: 1,
unit: 'days',
tickFormat: $window.d3.time.format('%H:%M')
}
];
var margin = {top: 0, right: 0, bottom: 0, left: 0},
width = $window.innerWidth,
height = $window.innerHeight - margin.top - margin.bottom,
xAxisHeight = $window.innerHeight - margin.top - ($window.innerHeight/2);
var start = $window.moment(Datasets.current.dates[0], 'DD/MM/YYYY'),
end = $window.moment(Datasets.current.dates[Datasets.current.dates.length - 1], 'DD/MM/YYYY'),
duration = $window.moment.duration(end.diff(start));
scope.zoomLevel = 3;
scope.page = 0;
scope.maxPage = duration.asMinutes()/scope.zoomLevels[scope.zoomLevel].value;
//$scope.tweets = Tweets.getTweets(start.format('x'), end.format('x'));
var rows = Math.floor(height/ 60);
console.log(rows + ' Rows');
var x = $window.d3.time.scale()
.domain([start.toDate(), start.clone().add(scope.zoomLevels[scope.zoomLevel].value, scope.zoomLevels[scope.zoomLevel].unit).toDate()])
.range([0, width]);
var y = $window.d3.scale.linear()
.domain([(rows/2) * -1,(rows/2)])
.range([height, 0]);
var xAxis = $window.d3.svg.axis()
.scale(x);
var yAxis = $window.d3.svg.axis()
.scale(y)
.tickSize(5)
.orient('right');
var svg = $window.d3.select('#timeline').append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + xAxisHeight + ')')
.call(xAxis)
.selectAll('text')
.attr('y', 15)
.attr('x', 0)
.attr('dy', '.35em');
svg.append('g')
.attr('class', 'y axis')
.call(yAxis);
Articles.get({startDate: start.format(), endDate: start.clone().add(scope.zoomLevels[scope.zoomLevel].value, scope.zoomLevels[scope.zoomLevel].unit).format(), limit: 20}, function(data){
var bar = svg.selectAll('.news')
.data(data.items)
.enter()
.append('g')
.attr('class', 'news')
.on('mouseover', function(d) {
console.log(d);
})
.on('mouseout', function(d) {
});
bar.append('rect')
.attr('width', 250)
.attr('height', 60)
.attr('x', function(d){return x($window.moment(d.date, 'YYYY-MM-DDTHH:mm:ss.000Z').format('x'));})
.attr('y', function(d){return y(2);})
bar.append('foreignObject')
.attr('width', 250)
.attr('height', 60)
.attr('x', function(d){return x($window.moment(d.date, 'YYYY-MM-DDTHH:mm:ss.000Z').format('x'));})
.attr('y', function(d){return y(2);})
.append('xhtml:p')
.attr("class","statement")
.text(function(d) { return d.title; });
});
scope.movePage = function(direction){
if(!(direction === 1 && scope.page === scope.maxPage) || !(direction === -1 && scope.page === 0)){
console.log('Paging allowed');
if(direction === 1){
start = start.add(scope.zoomLevels[scope.zoomLevel].value, scope.zoomLevels[scope.zoomLevel].unit);
x.domain([start.toDate(), start.clone().add(scope.zoomLevels[scope.zoomLevel].value, scope.zoomLevels[scope.zoomLevel].unit).toDate()]);
Articles.get({startDate: start.format(), endDate: start.clone().add(scope.zoomLevels[scope.zoomLevel].value, scope.zoomLevels[scope.zoomLevel].unit).format(), limit: 20}, function(data) {
console.log(data.items.length);
svg.selectAll('.news').data(data.items).transition().duration(1500).ease('sin-in-out');
svg.selectAll('g.axis.x').transition().duration(1500).ease('sin-in-out').call(xAxis);
});
scope.page++;
}
if(direction === -1){
start = start.subtract(scope.zoomLevels[scope.zoomLevel].value, scope.zoomLevels[scope.zoomLevel].unit);
x.domain([start.toDate(), start.clone().add(scope.zoomLevels[scope.zoomLevel].value, scope.zoomLevels[scope.zoomLevel].unit).toDate()]);
svg.selectAll('g.axis.x').transition().duration(1500).ease('sin-in-out').call(xAxis);
scope.page--;
}
}
};
scope.toggleZoom = function(direction){
if(!(direction === 1 && scope.zoomLevel === 6) || !(direction === -1 && scope.zoomLevel === 0)){
duration = $window.moment.duration(end.diff(start));
scope.zoomLevel = scope.zoomLevel + direction;
console.log(scope.zoomLevels[scope.zoomLevel].unit);
switch (scope.zoomLevels[scope.zoomLevel].unit){
case 'minutes':
scope.maxPage = duration.asMinutes()/scope.zoomLevels[scope.zoomLevel].value;
console.log(duration.asMinutes() + ' Minutes');
console.log((duration.asMinutes()/scope.zoomLevels[scope.zoomLevel].value) + ' Pages');
break;
case 'hours':
scope.maxPage = duration.asHours()/scope.zoomLevels[scope.zoomLevel].value;
console.log(duration.asHours() + ' Hours');
console.log((duration.asHours()/scope.zoomLevels[scope.zoomLevel].value) + ' Pages');
break;
case 'days':
scope.maxPage = duration.asDays()/scope.zoomLevels[scope.zoomLevel].value;
console.log(duration.asDays() + ' Days');
console.log((duration.asDays()/scope.zoomLevels[scope.zoomLevel].value) + ' Pages');
break;
}
x.domain([start.toDate(), start.clone().add(scope.zoomLevels[scope.zoomLevel].value, scope.zoomLevels[scope.zoomLevel].unit).toDate()]);
svg.selectAll('g.axis').transition().duration(1500).ease('sin-in-out').call(xAxis);
}
};
Update #1
I've been able to get new data to appear and older data to be removed using the following code in the Articles.get callback after line console.log(data.items.length)
var news = svg.selectAll(".news").data(data.items);
// Add new element
news.enter()
.append("g")
.attr("class", "news")
.transition()
.duration(1500)
.ease("sin-in-out");
news.append('rect')
.attr('width', 250)
.attr('height', 60)
.attr('x', function(d){return x($window.moment(d.date, 'YYYY-MM-DDTHH:mm:ss.000Z').format('x'));})
.attr('y', function(d){return y(2);});
news.append('foreignObject')
.attr('width', 250)
.attr('height', 60)
.attr('x', function(d){return x($window.moment(d.date, 'YYYY-MM-DDTHH:mm:ss.000Z').format('x'));})
.attr('y', function(d){return y(2);})
.append('xhtml:p')
.attr("class","statement")
.text(function(d) { return d.title; });
// Remove old elements
news.exit()
.attr("class", "news")
.transition()
.duration(1500)
.ease("sin-in-out")
.remove();
However the animations don't really work. Ultimately I want the svg elements that represent each news item to move to the left at the same pace the axis moves if the user clicks next page. Currently they appear/disappear instantly as the user clicks the forward and back buttons.
if(!(direction === 1 && scope.page === scope.maxPage) || !(direction === -1 && scope.page === 0)){
if(direction === 1){ ... }
if(direction === -1){ .. }
This is not easy to parse, are you sure it's possible for either of the inner code blocks to execute given those conditionals?
Related
I would like to display text on y-axis tick point they are like always same even if data changed here are the tick point text ["Low" , "Medium" , "High"]
i have tried a lot but im unable to find the perfect solution
tick points are alway 3 and they have to show this text on each tick point ["Low" , "Medium" , "High"]
here is the image that how i want
this is what i have implemented
im unable to set the text of ["Low" , "Medium" , "High"] by replacing [5, 10, 15]
here is the array of data for plotting graph
[
{ group: 'A', value: 5 },
{ group: 'B', value: 15 },
{ group: 'C', value: 10 },
{ group: 'D', value: 15 },
{ group: 'E', value: 10 },
{ group: 'F', value: 5 },
]
here is the bar chart code
var outerWidth = convertRemToPixels(40),
outerHeight = barGraphContainerRef.current.clientHeight;
var margin = {
top: convertRemToPixels(2),
right: convertRemToPixels(2),
bottom: convertRemToPixels(2),
left: convertRemToPixels(2),
},
width = outerWidth - margin.left - margin.right,
height = outerHeight - margin.top - margin.bottom;
var x = d3.scaleBand().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
var xAxis = d3.axisBottom(x);
var yAxis = d3.axisLeft(y).ticks(3);
function make_y_gridlines() {
return d3.axisLeft(y).ticks(3);
}
//Defenining the tooltip div
var chart = d3
.select(refe.current)
.attr('width', outerWidth)
.attr('height', outerHeight);
let tooltip = d3
.select('#root .tooltipContainer')
.style('position', 'absolute')
.style('top', 0)
.style('left', 0)
.style('display', 'none');
var gradient = chart
.select('.lGradient')
.attr('id', 'gradient')
.attr('x1', '0%')
.attr('y1', '00%')
.attr('x2', '0%')
.attr('y2', '100%')
.attr('spreadMethod', 'pad');
gradient
.select('.firstStop')
.attr('offset', '-100%')
.attr('stop-color', '#0170ac')
.attr('stop-opacity', 1);
gradient
.select('.secondStop')
.attr('offset', '100%')
.attr('stop-color', '#013c5d')
.attr('stop-opacity', 1);
var main = chart
.select('.chart')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
main
.select('.x-axis')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis)
.style('color', '#a4a4a4');
main
.select('.y-axis')
.attr('transform', 'translate(0,' + 0 + ')')
.call(yAxis)
.style('color', '#a4a4a4');
y.domain([
0,
d3.max(graphData, function (d) {
return d.value;
}),
]);
x.domain(
graphData.map(function (d) {
console.log(d);
return d.group;
})
).padding([0.5]);
main
.select('.x-axis')
.call(xAxis)
.selectAll('text')
.style('text-anchor', 'end')
.attr('dx', '.3em')
.attr('dy', '1em')
.style('font-size', '.8rem')
.attr('transform', 'rotate(0)')
.style('font-family', '"Roboto", sans-serif');
main
.select('.y-axis')
.call(yAxis)
.selectAll('text')
.attr('class', 'yAxiesText')
.attr('transform', 'rotate(-90)')
.attr('y', '-2em')
.attr('x', '.4em')
.attr('dy', '.71em')
.style('font-size', '.8rem')
.style('text-anchor', 'end')
.style('font-family', '"Roboto", sans-serif');
main
.select('.gridder')
.call(make_y_gridlines().tickSize(-width).tickFormat(''))
.attr('id', 'gridSystem');
var rects = main.selectAll('.paths').data(graphData.map(e => e.value));
const names = graphData.map(e => e.group);
rects
.join('path')
.attr('class', 'paths')
.attr('d', function (d, i) {
return RectangleBarWithRadiusOneSide(
x(names[i]),
y(d),
x.bandwidth(),
height - y(d),
5 // radius
);
})
here is the RectangleBarWithRadiusOneSide function for making onside border radius
const RectangleBarWithRadiusOneSide = (x, y, width, height, radius) => {
return (
'M' +
(x + radius) +
',' +
y +
'h' +
(width - 2 * radius) +
'a' +
radius +
',' +
radius +
' 0 0 1 ' +
radius +
',' +
radius +
'v' +
(height - 2 * radius) +
'v' +
radius +
'h' +
-radius +
'h' +
(2 * radius - width) +
'h' +
-radius +
'v' +
-radius +
'v' +
(2 * radius - height) +
'a' +
radius +
',' +
radius +
' 0 0 1 ' +
radius +
',' +
-radius +
'z'
);
};
here is the convertRemToPixels function
export const convertRemToPixels = rem => {
return rem * parseFloat(getComputedStyle(document.documentElement).fontSize);
};
I hope i have explained meaningfully
if i get any solution from the anyone that is so appreciatable
thanks advance!
You can use the tickFormat() function on a d3 axis generator to format the tick values. Create a scale to map the data values to your "Low", "High", "Medium" labels and pass it to tickFormat():
const tickScale = d3.scaleOrdinal()
.domain([...new Set(data.map(d => d.value))].sort(d3.ascending)) // keep unique values in ascending order
.range(["Low", "Medium", "High"])
.unknown(""); // other values are not displayed
const yAxis = d3.axisLeft(sy)
.ticks(3)
.tickFormat(tickScale);
A working prototype can be found here: https://codepen.io/ccasenove/pen/abKNOqa
I'm trying to learn SVG graphs, I've nearly completed my graph however I currently have overlapping bars, I believe this is due to a combination of my x axis return data and my json data. My x axis lists the months from my JSON data, but my JSON data can have multiple entries from the same month. and if this is the case then my entries will overlap for that month. What I need to do is return the month + day seperately, however I haven't been able to figure out how just yet.
JSON data example where there's 2 entries for the same month:
1: {dayNo: 2, monthNo: 7, monthName: "July", year: 2018, recordDate: "2018-07-02T00:00:00",…}
avgVolume: 3585.53
dayNo: 2
monthName: "July"
monthNo: 7
recordDate: "2018-07-02T00:00:00"
volumeLastYear: 4109.47
volumeToday: 3575.34
year: 2018
2: {dayNo: 30, monthNo: 7, monthName: "July", year: 2018, recordDate: "2018-07-30T00:00:00",…}
avgVolume: 3291.55
dayNo: 30
monthName: "July"
monthNo: 7
recordDate: "2018-07-30T00:00:00"
volumeLastYear: 3996.31
volumeToday: 3414.44
year: 2018
And here is the svg code:
$request.done(function (response) {
// remove old svg
$('svg').remove();
// create svg element
var $svg = d3.select('.card.mb-3.clickable-card.highlight')
.append('svg')
.attr('width', '100%')
.attr('height', '400px')
.attr('style', 'background-color: lavender')
.classed('svg display-svg', true);
// 2 determine size of svg element
var w = $svg.node().getBoundingClientRect().width;
var h = $svg.node().getBoundingClientRect().height;
// 10 chart margins
var chartMargin = new Object();
chartMargin.left = 40;
chartMargin.right = 25;
chartMargin.top = 25;
chartMargin.bottom = 40;
// 10 cont
h = h - chartMargin.bottom - chartMargin.top;
// Max volume
var maxVolume = d3.max(response, d => d.avgVolume);
// bar Margins / width
var barMargin = 10;
var barWidth = (w - chartMargin.left - chartMargin.right) / response.length;
// yScale
var yScale = d3.scaleLinear()
.domain([0, maxVolume])
.range([h, chartMargin.top]);
// xScale
var xScale = d3.scaleBand()
.domain(response.map(function (d) { return d.monthName; }))
.range([0, w - chartMargin.left - chartMargin.right])
.paddingInner(0.15);
// chartGroup
var chartGroup = $svg.append('g')
.classed('chartGroup', true)
.attr('transform', 'translate(' + chartMargin.left + ',' + chartMargin.top + ')');
// 5
var barGroups = chartGroup
.selectAll('g')
.data(response);
// 6
var newBarGroups = barGroups.enter()
.append('g')
.attr('transform', function (d, i) {
return 'translate('
+ (xScale(d.monthName))
+ ','
+ (yScale(d.avgVolume))
+ ')';
})
// yAxis label
var yAxis = d3.axisLeft(yScale);
chartGroup.append('g')
.classed('axis y', true)
.attr('transform', 'translate(' + -20 + ', ' + h / 2 + ')')
.append('text')
.attr('transform', 'rotate(-90)')
.attr('dx', '-0.8em')
.attr('dy', '0.25em')
.attr("text-anchor", 'middle')
.text("Reservoir Volume (ML)");
// xAxis label
var xAxis = d3.axisBottom(xScale);
chartGroup.append('g')
.attr('transform', 'translate(0,' + h + ')')
.classed('axis x', true)
.call(xAxis);
newBarGroups
.append('rect')
.attr('x', 0)
.attr('height', function (d, i) {
return h - yScale(d.avgVolume);
})
// animation
.style('fill', 'transparent')
.transition().duration(function (d, i) { return i * 500; })
.delay(function (d, i) { return i + 200; })
.attr('width', barWidth - barMargin)
.style('fill', function (d, index) {
return "hsl(240, 100%, " + (d.avgVolume / maxVolume * 80) + "%)"
});
// bar text
newBarGroups
.append('text')
.attr('transform', 'rotate(-45)')
.attr('text-anchor', 'middle')
.attr('x', function () { return 0; })
.attr('y', 50)
.attr('fill', 'white')
.text(function (d, i) { return d.avgVolume; })
.style('font-size', '1em')
});
in the xScale, I have tried changing the return data from
return d.monthName
to
return d.monthName + ' ' + d.dayNo
But then all my bars overlap and I get this error for each g element:
d3.js:1211 Error: attribute transform: Expected number, "translate(undefined,25.183…".
Here is what I have currently with returning d.monthName:
Here is d.monthName + ' ' + d.dayNo:
Nevermind sorry, I fixed it
I needed to change
var newBarGroups = barGroups.enter()
.append('g')
.attr('transform', function (d, i) {
return 'translate('
+ (xScale(d.monthName))
+ ','
+ (yScale(d.avgVolume))
+ ')';
})
to
var newBarGroups = barGroups.enter()
.append('g')
.attr('transform', function (d, i) {
return 'translate('
+ (xScale(d.monthName + ' ' + d.dayNo))
+ ','
+ (yScale(d.avgVolume))
+ ')';
})
In my d3 bar chart, I should have Y-axis dynamic ticks and grid lines for integer values (no ticks and grid lines for decimal values). But its not working , Please help me on this
var itemData =[{month: "MARCH", year: 2018, number: 26, date:1519842600000},{month: "MAY", year: 2018, number: 34, date: 1525113000000}];
createChart(itemData )
createChart(itemData) {
const router_temp = this.router;
const element = this.chartContainer.nativeElement;
const factoryId = itemData['id'];
const data = itemData.data;
d3.select(element).selectAll('*').remove();
const div = d3.select('body').append('div')
.attr('class', 'tooltip-bar-chart')
.style('display', 'none');
const svg = d3.select(element),
margin = {top: 10, right: 10, bottom: 20, left: 30},
width = +this.el.nativeElement.offsetWidth - margin.left - margin.right,
height = +svg.attr('height') - margin.top - margin.bottom;
const x = d3.scaleBand().range([10, width - 10]).padding(1),
y = d3.scaleLinear().rangeRound([height, 0]);
// add the X gridlines
svg.append('g')
.attr('class', 'grid')
.attr('transform', 'translate(30,' + height + ')')
.call(make_x_gridlines()
.tickSize(-height)
.tickFormat('')
);
// add the Y gridlines
svg.append('g')
.attr('class', 'grid')
.attr('transform', 'translate(30, 10)')
.call(make_y_gridlines()
.tickSize(-width)
.tickFormat('')
);
const g = svg.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
x.domain(data.map(function(d) { return d.month; }));
y.domain([0, d3.max(data, function(d) { return d.number; })]);
//
g.selectAll('.bar')
.data(data)
.enter().append('rect')
.attr('class', 'bar')
.attr('x', function(d) { return x(d.month); })
.attr('y', function(d) { return y(d.number); })
.attr('width', 12)
.attr('height', function(d) { return height - y(d.number); })
.on('mouseover', function(d) {
div.style('display', 'block');
div.html(
'<div class=\'main\'>' +
'<div class=\'month\'>' +
d.month +
'</div>' +
'<div class=\'number\'>' +
d.number +
'</div>' +
'<div class=\'bottom\'>' +
'Number of Applications Transformed & Migrated' +
'</div>' +
'</div>'
)
.style('left', (d3.event.pageX + 15) + 'px')
.style('top', (d3.event.pageY - 50) + 'px');
})
.on('mouseout', function(d) {
div.style('display', 'none');
})
const dataLength = data.length;
const xPositions = [];
for (let i = 0; i < dataLength; i += 1) {
xPositions.push(x(data[i].month) + margin.left);
}
const newX = d3.scaleOrdinal().range(xPositions);
const xScale = newX.domain(itemData.xlabels);
svg.append('g')
.attr('class', 'axis axis--x')
.attr('transform', 'translate(0,' + (height + 10) + ')')
.call(d3.axisBottom(xScale));
g.append('g')
.attr('class', 'axis axis--y')
.call(d3.axisLeft(y).ticks(5));
// gridlines in x axis function
function make_x_gridlines() {
return d3.axisBottom(x)
.ticks(1);
}
// gridlines in y axis function
function make_y_gridlines() {
return d3.axisLeft(y)
.ticks(5);
}
}
}
Here is sample
I created a fiddle.
that while I had to change a few things to account for no angular, but shows the axis gridlines and labels aligned. I moved everything into one g group is is then translated and the individual components are no longer translated.
I am using normal d3.js in my react application. problem is this code is being executing multiple times if I put my code in componentDidMount it is running only single time. but it is giving my value undefined. if I add componentDidUpdate it is giving value properly but running svg componenet multiple times. now don't know what to do?
below is my code
componentDidMount(){
this.processData(this.props);
}
componentDidUpdate(prevProps,prevState){
if(this.props.potentialBrandReturn== undefined)
this.processData(this.props);
return false;
}
processData(props){
var data = [
{
label: 'Return',
value: props.currentYearReturn,
potential: props.potentialComReturn,
color: '#aa3939'
},
{
label: 'Brand',
value: props.currentYearBrand,
potential: props.potentialBrandReturn,
color: '#2c4770'
}
];
var div = d3.select('body').append('div').attr('class', 'toolTip');
var axisMargin = 20,
margin = 40,
valueMargin = 4,
width = 550,
height = 250,
barHeight = 40,
barPadding = 30,
data,
bar,
svg,
scale,
scaleMark,
xAxis,
labelWidth = 0;
var max = d3.max(data, function(d) {
return d.potential;
});
svg = d3
.select('#customChart2')
.classed('svg-container1', true) //container class to make it responsive
.append('svg')
//responsive SVG needs these 2 attributes and no width and height attr
.attr('preserveAspectRatio', 'xMinYMin meet')
.attr('viewBox', '0 0 600 200')
//class to make it responsive
.classed('svg-content-responsive', true);
scale = d3.scale
.linear()
.domain([0, max])
.range([0, 400 - margin * 2 - labelWidth]);
bar = svg.selectAll('g').data(data).enter().append('g');
bar.attr('class', 'bar').attr('cx', 0).attr('transform', function(d, i) {
return (
'translate(' +
margin +
',' +
(i * (barHeight + barPadding) + barPadding) +
')'
);
});
//appending lable
bar
.append('text')
.attr('class', 'label')
.attr('y', barHeight / 2)
.attr('dy', '.35em') //vertical align middle
.text(function(d) {
return d.label;
})
.each(function() {
labelWidth = Math.ceil(Math.max(labelWidth, this.getBBox().width)) + 5;
});
//appending Potential rect
bar
.append('rect')
.attr('transform', 'translate(' + labelWidth + ', 0)')
.attr('height', barHeight)
.style('fill', 'transparent')
.style('stroke', function(d) {
return d.color;
})
.attr('width', function(d) {
return scale(d.potential);
})
.on('mouseover', function() {
return tooltip.style('visibility', 'visible');
})
.on('mousemove', function(d) {
return tooltip
.style('top', d3.event.pageY - 10 + 'px')
.style('left', d3.event.pageX + 10 + 'px')
.text('Potential Value is $' + d.potential);
})
.on('mouseout', function() {
return tooltip.style('visibility', 'hidden');
});
//appending main bar for the chart
bar
.append('rect')
.attr('transform', 'translate(' + labelWidth + ', 0)')
.attr('height', barHeight)
.style('fill', function(d) {
return d.color;
})
.attr('width', function(d) {
return scale(d.value);
})
.on('mouseover', function() {
return tooltip.style('visibility', 'visible');
})
.on('mousemove', function(d) {
return tooltip
.style('top', d3.event.pageY - 10 + 'px')
.style('left', d3.event.pageX + 10 + 'px')
.text('Actual Value is $' + d.value);
})
.on('mouseout', function() {
return tooltip.style('visibility', 'hidden');
});
// appending text for rect Potential
bar
.append('text')
.attr('transform', 'translate(' + labelWidth + ', 0)')
.attr('y', barHeight / 2)
.attr('dy', '.35em') //vertical align middle
.attr('x', 10)
.attr('dx', function(d) {
return scale(d.potential);
})
.text(function(d) {
return '$' + d.potential + ' Potential';
});
//appending value in side the rectangle
bar
.append('text')
.attr('class', 'value')
.attr('y', barHeight / 2)
.attr('dx', function(d) {
var temp = scale(d.value) + labelWidth;
return temp - 10;
}) //margin right
//.attr("dx", valueMargin + labelWidth + margin) //margin right
.attr('dy', '.35em') //vertical align middle
.attr('text-anchor', 'start')
.text(function(d) {
return '$' + d.value;
});
// for tooltip message
var tooltip = d3
.select('body')
.append('div')
.attr('class', 'toolTip1')
.style('position', 'absolute')
.style('z-index', '10')
.style('visibility', 'hidden');
// appending the chart to html
svg
.insert('g', ':first-child')
.attr('class', 'axisHorizontal')
.attr(
'transform',
'translate(' +
(margin + labelWidth) +
',' +
(height - axisMargin - margin) +
')'
);
// .call(yAxis);
try this
constructor(props) {
super(props);
this.state = {
isDataPopulated: false
};
}
componentDidMount(){
// delte call to this.processData()
}
componentDidUpdate(prevProps,prevState){
let potentialBrandReturn = this.props.potentialBrandReturn === undefined;
if(!potentialBrandReturn && !this.state.isDataPopulated)
this.processData(this.props);
this.setState({isDataPopulated : true})
}else{
return false;
}
}
I am trying to modfiy the example stacked area chart here
currently it gives me an error as such:
Error: <path> attribute d: Expected number, "MNaN,22770LNaN,21…".
at this line: .attr('d', area);
This is my code so far:
let margin = {top: 20, right: 60, bottom: 30, left: 60},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
let parseDate = d3.timeParse('%m/%d/%Y');
let x = d3.scaleTime().range([0, width]),
y = d3.scaleLinear()
.range([height, 0]),
z = d3.scaleOrdinal()
.domain(['Detractors', 'Passives' , 'Promoters'])
.range(['#e81123', '#a0a1a2', '#7fba00']);
let stack = d3.stack();
let area = d3.area()
.x(function(d) { return x(d.data.date); })
.y0(function(d) { return y(d.y0); })
.y1(function(d) { return y(d.y0 + d.y); });
let g = this._svg.append('g')
.attr('transform', 'translate(' + 0 + ',' + margin.top + ')');
let data = [
{ 'date': '04/23/12' , 'Detractors': 20 , 'Passives': 30 , 'Promoters': 50 },
{ 'date': '04/24/12' , 'Detractors': 32 , 'Passives': 19 , 'Promoters': 42 },
{ 'date': '04/25/12' , 'Detractors': 45 , 'Passives': 11 , 'Promoters': 44 },
{ 'date': '04/26/12' , 'Detractors': 20 , 'Passives': 13 , 'Promoters': 64 }];
// console.log(myData.map(function (d) { return d.key; }));
// console.log('KEYS: '+ keys);
x.domain(d3.extent(data, function(d) {
console.log(parseDate(d.date));
return parseDate(d.date); }));
// let keys = (d3.keys(data[0]).filter(function (key) { return key !== 'date'; }));
z.domain(d3.keys(data[0]).filter(function (key) { return key !== 'date'; }));
// z.domain(keys);
stack.keys(d3.keys(data[0]).filter(function (key) { return key !== 'date'; }));
let layer = g.selectAll('.layer')
.data(stack(data))
.enter().append('g')
.attr('class', 'layer');
layer.append('path')
.attr('class', 'area')
.style('fill', function(d) {
// console.log('d.key: ' + d.key);
console.log('d.key: ' + d.key + ' color: ' + z(d.key));
return z(d.key); })
.attr('d', area);
layer.filter(function(d) { return d[d.length - 1][1] - d[d.length - 1][0] > 0.01; })
.append('text')
.attr('x', width - 6)
.attr('y', function(d) { return y((d[d.length - 1][0] + d[d.length - 1][1]) / 2); })
.attr('dy', '.35em')
.style('font', '10px sans-serif')
.style('text-anchor', 'end')
.text(function(d) {
// console.log('key label: ' + d.key);
return d.key; });
g.append('g')
.attr('class', 'axis axis--x')
.attr('transform', 'translate(0,' + height + ')')
.call(d3.axisBottom(x));
// console.log(height);
g.append('g')
.attr('class', 'axis axis--y')
.call(d3.axisLeft(y).ticks(5, '%'));
}
I am just trying to parse the array so I can have the date, keys and value parsing properly. I just can't seem to get it right.
Much help is appreciated!!
The specific error that you are getting is due to the format of the date in your data. You have a line commented out in your code: let parseDate = d3.timeParse("%m/%d/%Y"); You do want to use the d3.timeParse function to transform your data to date format:
let tp = d3.timeParse("%m/%d/%y")
for(var i=0; i<data.length;i++){
data[i].date = tp(data[i].date)
}