I was using nvd3.js to create a simple stacked bar chart as described here
I added the code mentioned in the link in an angular directive as follows:
app.directive('stackBar', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
nv.addGraph(function() {
var chart = nv.models.multiBarChart()
/*.transitionDuration(350)*/
.reduceXTicks(true) //If 'false', every single x-axis tick label will be rendered.
/*.rotateLabels(0) */ //Angle to rotate x-axis labels.
.showControls(true) //Allow user to switch between 'Grouped' and 'Stacked' mode.
.groupSpacing(0.1) //Distance between each group of bars.
chart.xAxis
.tickFormat(d3.format(',f'));
chart.yAxis
.tickFormat(d3.format(',.1f'));
d3.select(element[0])
.datum(exampleData())
.call(chart);
nv.utils.windowResize(chart.update);
return chart;
});
//Generate some nice data.
function exampleData() {
return stream_layers(3,10+Math.random()*100,.1).map(function(data, i) {
return {
key: 'Stream #' + i,
values: data
};
});
}
}
}
});
Here's my HTML:
<td class="centered" colspan="5">
<div stack-bar>
</div>
</td>
But, I am getting the following error:
Uncaught ReferenceError: stream_layers is not defined
Any idea where I am going wrong?
Also, 'transitonDuration' was also not working so I commented it out. I initially thought, this maybe some problem related to the version of d3, but I am using the latest version and still the problem persists.
EDIT:
Huang Feng's answer helped me get rid of the error. But instead of getting any chart I am getting a lot of text. Here's a screenshot:
Any idea why?
Also, the directive is in an ng-repeat, and thats why there are multiple rows as in the screenshot.
This is because you don't define the stream_layers function, and it's also not a function in nvd3 lib.
It's defined here:
http://nvd3.org/assets/js/data/stream_layers.js
If you want to use it, you should include this lib in the html like:
<script src="../stream_layers.js"></script>
If you want a detail example, here is one for your reference:
http://bl.ocks.org/mbostock/3943967
I encountered similar output (and errors), and there were 3 things needed to fix this:
1. Add stream_layers.js to path
As mentioned by huan feng, you need to include the stream_layers.js function to generate fake data for the example. I downloaded the source code from nvd3.org, and the stream_layers.js file was located in novus-nvd3-8ceb270/test/stream_layers.js (you'll most likely need to update at least that first part of your path based on the latest shortened git hash.
2. Replace transitionDuration with duration
Another issue is that the API has changed for NVD3. After inspecting the source code (line 456 in novus-nvd3-8ceb270/src/models/multiBarChart.js), the transitionDuration method has been replaced with just duration. Here is a snippet from your code above with the duration option corrected:
var chart = nv.models.multiBarChart()
.duration(350)
.reduceXTicks(true) //If 'false', every single x-axis tick label will be rendered.
/*.rotateLabels(0) */ //Angle to rotate x-axis labels.
.showControls(true) //Allow user to switch between 'Grouped' and 'Stacked' mode.
.groupSpacing(0.1) //Distance between each group of bars.
3. Create an SVG
Lastly, your screen shot is only showing text. I think the reason for this is because you were trying to append your chart to a div when it needs to be appended to an SVG element. One way of correcting this is by appending an svg to element[0], and then selecting that svg to use with your vizualization:
var svg = d3.select(element[0])
.append('svg')
.attr('id', 'my-bar-chart-svg')
d3.select('#my-bar-chart-svg')
.datum(exampleData())
.call(chart);
If you're still not seeing anything, you might need to fiddle with the width and height of the SVG element you created to append your chart to. Here's one quick hack for testing:
var svg = d3.select(element[0])
.append('svg')
.attr('id', 'my-bar-chart-svg')
.attr('width', 800)
.attr('height', 400)
d3.select('#my-bar-chart-svg')
.datum(exampleData())
.call(chart);
Related
I am trying to make just the axis label of the selected item bold, so that it is more apparent to users what they have selected. I am using a composite bar chart to compare two values and have turned the labels -90 so that they are inside the bars. I have been able to make the labels clickable, with help from Gordon here: dc.js barChart - click on x-axis ticks and labels
I am counting clicks so that users can select and deselect from clicking on the label, however with some of the smaller values, users cannot tell which items they have filtered on in any specific chart.
I am able to select, de-select on clicks, I have tried select('tick-text').attr('style', 'font-weight: bold;');
This bolds the first item in the x-axis, no matter which selection is made.
When I use selectAll('.x text) it changes all.
Relevant portion of the code:
ByTopLvl.on('pretransition',function() {
ByTopLvl.selectAll('g.x text')
.style('text-anchor', 'start')
.attr('transform', 'rotate(-90),translate(10, -10)');
ByTopLvl.select('.axis.x')
.selectAll('.tick text')
.on('click.custom', function (d) {
var clicks = $(this).data('clicks');
if (!clicks) {
ByTopLvl.replaceFilter(d)
.select('.tick text')
.attr('style', 'font-weight: bold;');
ByTopLvl.redrawGroup();
} else {
ByTopLvl.select('.tick text')
.attr('style', 'font-weight: normal;');
ByTopLvl.filterAll();
dc.redrawAll();
}
$(this).data("clicks", !clicks);
});
I would expect that when I click on the label, only the label clicked would be bold.
First off, whenever possible, I would suggest using the built-in selection/filter state rather than trying to keep track of clicks yourself. Otherwise they are bound to get out of sync at some point.
If you have the boldness of the ticks driven by the active filters, then you'll get the same behavior whether the bar or the tick was clicked, and you can be certain that exactly the ticks in the filters are the bolded ones:
CSS
.dc-chart g.axis.x text.selected {
font-weight: bold;
}
JS
chart.on('filtered', function(chart) {
var filters = chart.filters();
chart.selectAll('.axis.x .tick text').classed('selected', function(d) {
return filters.includes(d);
})
})
[Side note since I'm not answering your exact question: if you want to make your code work, you could do something like filter the selection based on d:
ByTopLvl.select('.tick text').filter(function(d2) { return d2 === d; })
or in your case, this is the clicked tick, so d3.select(this) should also work. But I think you'll run into a lot of bugs that way.]
Similarly, you can simplify your click behavior by tying into the built-in filter behavior, which already toggles:
chart.on('pretransition', function(chart) {
chart.select('.axis.x')
.selectAll('.tick text')
.on('click.select', function(d) {
chart.filter(d);
chart.redrawGroup();
});
});
Yeah, it's weird that the built-in filter function toggles, but that's just the way that dc.js evolved.
Here's a demo fiddle.
In a composite
Composite charts in dc.js are a little bit of a confusing mess.
The filter selection is shared between the parent and child, except they sort also handle it separately.
Unfortunately when I have to troubleshoot composite charts, I just try different things until it works, and never fully understand what's going on. I think it's just too complicated for my brain. :-O
Anyway, this worked...
Keep a reference to the inner bar chart:
var chart = dc.compositeChart('#test'), bar;
chart
.width(768)
.height(380)
.x(d3.scaleBand())
.xUnits(dc.units.ordinal)
.brushOn(false)
.xAxisLabel('Fruit')
.yAxisLabel('Quantity Sold')
.dimension(fruitDimension)
.group(sumGroup)
._rangeBandPadding(0.05)
.compose([
bar = dc.barChart(chart)
.barPadding(0.1)
.outerPadding(0.05)
])
When responding to the click, filter the child bar chart instead of the parent:
chart.on('pretransition', function(chart) {
chart.select('.axis.x')
.selectAll('.tick text')
.on('click.select', function(d) {
bar.filter(d);
chart.redrawGroup();
});
});
Listen to the filtered event of the child bar chart and apply axis bolding to the parent composite chart:
bar.on('filtered', function(chart) {
var filters = chart.filters();
chart.selectAll('.axis.x .tick text').classed('selected', function(d) {
return filters.includes(d);
})
})
Whoo. I guess it's sorta.. consistent? The child bar chart is the source of truth for the filters. Maybe I'm rationalizing.
New fiddle version.
I am trying to build a D3 bar chart visualization that can be updated using a drop down menu.
So far, I have successfully created the bar chart and the drop down menu such that when I change the selection in the menu, the bars are updated to their new values.
However, I am having problems with the bar labels updating appropriately. Even though I include the same .exit.remove() function for the labels as for the bars, the old labels remain on the newly updated chart.
Image of the updated chart w/ problem labels
Test csv file
Test code
Apologies for the links. I'm new to JSFiddle, and I couldn't figure out how to easily transfer my example.
You forgot to add class attribute while appending the text
You also need to change the text while updating
Hope this helps
bartexts.transition().duration(250)
.attr("y", function(d, i) {return yScale(d) - 10})
.text(function(d) {return d});
I’m new to to d3 and have combined mbostock’s stacked bar graph example ( http://bl.ocks.org/mbostock/3886208 ) with a map to show data (canada.json)
I would like the graph to display an updated chart on the d3.mouseover event of the province:
http://gao8a.github.io/ (something like this)
Unfortunately, I was only able to get the axises to display. It's showing either multiple or the same axis overlapping:
(These will take ~ 3 seconds to load)
Multiple:
http://bl.ocks.org/GAO8A/566e238a72e5ebd1e2c1
Same Axis overlap
http://bl.ocks.org/GAO8A/64f94bb494c4a73f2bf6
I understand I probably need a ‘mouseout’ event to delete the previous but I’m not quite sure how to design that either.
Can anyone point out what I’m doing wrong and how I should be loading the data?
PS:
I was going to make a jsfiddle but can’t seem to get it to get it to work with my hosted canada.json data.
https://raw.githubusercontent.com/GAO8A/GAO8A.github.io/master/canada.json
Thanks
Your issue is that you keep adding the axes in the tooltip element. Unfortunately this creates the overlaps. What you could do is add the axes once, and the readjust their domain with the new values that correspond to the element being hovered.
So in essence if you could add the following lines:
var X_AXIS = tooltip.append("g").attr("class", "x axis x-axis").attr("transform", "translate(0," + height + ")");
X_AXIS.call(xAxis)
X_AXIS.append("text").attr("dy", "3em").attr("dx", "50em").style("text-anchor", "end").text("Month");
var Y_AXIS = tooltip.append("g").attr("class", "y axis y-axis")
Y_AXIS.call(yAxis)
Y_AXIS.append("text").attr("transform", "rotate(-90)").attr("dy", "-3em").attr("dx", "-8em").style("text-anchor", "end").text("Temperature (Celcius)");
just before adding your map, this would in a sense 'initialize' the axes.
So further, in your mouseover handler, you could add the following lines, just after you determine your x and y domains.
X_AXIS.call(xAxis)
Y_AXIS.call(yAxis)
or better still, to add some transition:
X_AXIS.transition().duration(400).call(xAxis)
Y_AXIS.transition().duration(400).call(yAxis)
This way, you don't keep adding axes, you just readjust the ones currently intialized.
Hope this helps.
I'm trying to teach myself D3 with examples from http://bl.ocks.org/mbostock.
I took the scatterchart and I'm trying to load various data depending on what menu-item is active.
Everything is working fine, but I got one problem I just can't solve.
The xAxis should update itself depending on the values from the data linked to the menu item.
I was searching the web for an answer, but couldn't find one that worked for me.
I think the problem (and solution) lies in this part of the code;
function updateChart() {
svg.selectAll('.dot')
.transition()
.duration(1000)
.attr('cx', function(d) {
return x(d.data[parameter]);
})
svg.select(".x.axis")
.call(xAxis);
}
I made this JSFiddle to make it more understandable.
Here's what's going on.
You successfully generated your xAxis with the correct x scale in the first go around, however
You didn't update your xAxis with the new domain of data
You were right in that you had to re-update your scales whenever you click on your labels.
I've done a couple of things:
Add a sourceData variable after you've coerced your numbers, for all your functions to reference
Add a updateXScale(data) function that will simply update your x scale's domain
Have it called every time you click a label. Not only will this fix your x scale, it will also enable the correct scaling of your x-coordinates for your .dot's.
Here's what it looks like all together. I've created a fiddle that has a working version of your example.
And here's your updateChart function for reference:
function updateChart() {
updateXScale(sourceData);
svg.selectAll('.dot')
.transition()
.duration(1000)
.attr('cx', function(d) {
return x(d.data[parameter]);
});
svg.select(".x.axis")
.call(xAxis);
}
I'm trying to replicate this Focus+Context via Brushing example. I'm including the same layout, but with a scatterplot instead of a line/area plot.
I started working off this example I found which combines the area plot and a scatterplot. However, when I scrap the area plot, I lose the zoom/focus capability.
My last step (thus far unsuccessful) is to make the brush (small focus bar on the bottom) actually respond to the main panel (make it adjust/zoom in when smaller time periods are selected in the brush). The brush adjusts the axis as it should, but I just haven't been able to make the brush actually adjust/zoom the points on the main scatterplot. I'm not trying plot anything in the brush - there will be a lot of points, so keeping the brush with a grey background and no points is fine.
here's my fiddle: http://jsfiddle.net/fuqzp580/3/
Sidenote: I can't quite get the jsfiddle to work with the way I'm using d3.csv, so I coded up a slightly altered version with dummy data in lieu of using d3.csv. However, I included the d3.csv code (commented out), just in case that could be a cause for my problem.
I'm new to d3 so any pointers or ideas welcome!
Here's an updated fiddle with the dots zooming on the points in the main panel: http://jsfiddle.net/henbox/3uwg92f8/1/
You were very close, I just made 3 small changes:
Firstly, uncommented the code you already had in function brushed() for selecting the dots
Secondly, defined mydots globally (since you were only doing it inside initialize() and it needs to be used beyond this scope). Added this on line 55:
var mydots = focus.append("g");
And last (and most importantly), I changed the definition for xMap from
xMap = function(d) { return x2(d.time); }
to
xMap = function(d) { return x(d.time); }
When brushing, it's the x scale that gets updated, not the x2