I try to dynamically change the number of ticks on an axis via an input field
// updated by an input field
var nNumberOfTicks;
function updatenumberofticks(nValue) { nNumberOfTicks = nValue; }
// definition of the axis
var xAxis = d3.svg.axis()
.orient('bottom')
.ticks(nNumberOfTicks)
.scale(xScale);
but it doesn't work. See here: https://jsfiddle.net/stefanooo/cn2xo56w/3/.
This example works perfectly when changing xmin. What do I have to change to make it work for the number of ticks also?
You need to give the updated number of ticks to the axis component after changing it, e.g.
d3.select("#numberofticks").on("change", function() {
updatenumberofticks(+this.value);
vis.select('.xaxis').transition().call(xAxis.ticks(+this.value));
});
Complete example here.
Related
I'm building a data dashboard using DC.js and was wondering if it was possible to change the color of the slices in a pie chart dynamically based on the value in the field it is referring to.
Basically I've built a pie chart aggregating the costume colors of different superheroes and I'd love to be able to color each slice with the color it is referring to - so the slice for 'Black' is colored black, the slice for 'Green' is colored green and so forth.
I'm fairly new to DC.js so accept that it may not be possible, but wanted to throw it out there and see if it could be done!
I tried including an array within .ordinalColors but couldn't figure out if there was a way to pull in the data from the field dynamically. I'm assuming that I'd have to change the data in the .csv file to a string that could be recognised as a color reference, but not sure how to go about doing that.
function show_costume_color(ndx) {
var costume_color_dim = ndx.dimension(dc.pluck('Costume Colour'));
var costume_color = costume_color_dim.group();
dc.pieChart('#costume-color')
.width(500)
.height(500)
.radius(500)
.innerRadius(100)
.slicesCap([7])
.transitionDuration(1500)
.dimension(costume_color_dim)
.group(costume_color);
}
CSV data comes in the below format
ID,name,Gender,Eye color,Race,Hair color,Publisher,Alignment,Superpower,Superpower Strength Level,Costume
Colour
0,A-Bomb,Male,Yellow,Human,No Hair,Marvel Comics,Good,Superhuman
Strength,10,None
1,Abin Sur,Male,Blue,Ungaran,No Hair,DC Comics,Good,Cosmic Power,40,Green
Yes, of course. Everything is specified dynamically in dc.js.
Assuming you are using dc.js v3 (and d3 v4+) the way I would suggest doing this is by creating another CSV file with the color assignments you want, something like
Name, RGB
Red, #ff1122
Blue, #1133ff
...
Then you can load the second file in parallel with your data using Promise.all(),
Promise.all([d3.csv('data.csv'), d3.csv('colors.csv')])
.then(function(data, colors) {
// rest of code will go here
});
ordinalColors is a nice convenience method, but if you want complete control, and to understand exactly what's going on, it's better to supply your own color scale. In this case, we want an ordinal scale, which maps specific discrete values to specific colors.
Under the covers, dc.js always deals with colors by using the colorAccessor to fetch a value for the the item, and then mapping this value using a color scale. You can think of the value that the accessor returns as a "color name", which is pretty convenient because it's exactly what you want here.
So you can populate a d3.scaleOrdinal with the domain of color names and the range of RGB colors:
var colorScale = d3.scaleOrdinal()
.domain(colors.map(row => row.Name))
.range(colors.map(row => row.RGB));
Now supply it to your chart using .colors():
chart.colors(colorScale);
What's really handy about this approach is that you can supply the same color scale for multiple charts, in order to make sure they are consistent. This is something that you don't get automatically in dc.js, because charts don't know very much about each other.
So, I managed to figure it out through an extensive period of trial and error and now I'm off and away with my dashboard. Thanks for your help, Gordon - it really made the difference! It needs a bit of tidying up but my working test code is below.
// Bring in data from both csv files
Promise.all([d3.csv("../data/heroes_information.csv"),
d3.csv("../data/costume_colors.csv")])
.then(function(data) {
// Tidy up data before use
data.forEach(function(d) {
d.Height = +d.Height;
d.Weight = +d.Weight;
d.Strength = +d.Strength;
});
// Bring in colorScale to dynamically color pie chart slices
var ndxcol = crossfilter(data[1]);
var colorScale = d3.scaleOrdinal()
.domain(data[1].map(row => row.Name))
.range(data[1].map(row => row.RGB));
// Bring in superhero data
var ndx = crossfilter(data[0]);
// Define chart types
var publisherSelector = dc.selectMenu('#publisher-selector')
var genderChart = dc.rowChart('#gender-balance');
// Define chart dimensions
var publisherChoice = ndx.dimension(dc.pluck('Publisher'));
var genderBalance = ndx.dimension(dc.pluck('Gender'));
// Define chart groups
var genderNumber = genderBalance.group();
var publisherNumber = publisherChoice.group();
// Draw charts
publisherSelector
.dimension(publisherChoice)
.group(publisherNumber);
genderChart
.width(500)
.height(200)
.margins({ top: 30, right: 30, bottom: 30, left: 30 })
.dimension(genderBalance)
.group(genderNumber)
.gap(6)
.colors(colorScale)
.transitionDuration(500)
.x(d3.scaleOrdinal())
.elasticX(true);
dc.renderAll();
});
I am trying to create a "proper" version of the logo at IFMMS Logo
using the graticule feature of d3.js.
At https://gist.run/?id=22361573b05b541ac9799037116aea8d you'll find the current state of the code - specifically version https://gist.run/?id=22361573b05b541ac9799037116aea8d&sha=dd6aef5c64e3ac4c1c917fd5e3c7bbf4b91c75a8
In a loop i am trying to set the graticule steps for different scales of the logo draft.
var lonsteps=6+col*2;
var latsteps=10;
var title='IFMMS Logo';
createLogo(title,id,cx,cy,scale,15,lonsteps,latsteps,debug);
in the "createLogo" function i am using these steps as outlined in the answer:
https://stackoverflow.com/a/19033063/1497139
I get
instead of
so instead of having 6/8/10 steps for the longitudinal grid lines I get three times 6 steps. It seems as if the first setting "overrides" all others although i assign different values.
What is causing this and how can it be fixed?
If you pass a parameter (svgid) to a function use it and don't use the global variable (id) with the same value.
function createLogo(title,svgid,cx,cy,scale,strokeScaleFactor,lonsteps,latsteps,debug) {
// ...
ggraticule.selectAll("path.feature"+svgid)
.data(graticule.lines)
.enter().append("path")
.attr("class", "feature"+svgid)
.attr("d", path);
// ...
}
One indication of the problem can be that your scale is not working.
The reason a HTML ID should be unique.
fix it by making the graticule defs id unique and use this new id.
var ggraticule=defs.append("g")
.attr("id","graticule-"+svgid)
.attr("class", "graticule")
.attr("stroke-width",scale/strokeScaleFactor);
// ...
use(svg,"graticule-"+svgid);
You still have a problem with the background, choose a color and see the effect.
In D3 v5+ you can use the step method on your geoGraticules like so:
var graticules = d3.geoGraticule().step([15, 15])
The default step is 10° and 10° for longitude and latitude.
I'd like to add a unit to the y-axis tick labels in plotly.js. How might I do this? Adding a ticktext and tickval property for yaxis layout doesn't seem to have the effect I thought, as it does with an xaxis.
As per your own comment, and per here
ticksuffix(string)
You can add this as part of the x or y axis option as part of the layout text for your plot.
e.g.
var layout = {
yaxis:
{
ticksuffix: "suffix"
}
};
How can I replace below lines with the new version of D3 API?
I have already replaced scale.linear() with scaleLinear()
var xRange = d3.scaleLinear()
.domain([OIResults.min,OIResults.max]).range([40, 360]);
var yRange = d3.scaleLinear()
.domain(y_domain).range([360, 40]);
Below Lines need to be replaced according to the new API:
var xAxis = d3.svg.axis().scale(xRange).tickFormat(function(d) { return d.x;});
var yAxis = d3.svg.axis().scale(yRange).orient("left");
The D3 v4 API is here. According to the changelog:
D3 4.0 provides default styles and shorter syntax. In place of d3.svg.axis and axis.orient, D3 4.0 now provides four constructors for each orientation: d3.axisTop, d3.axisRight, d3.axisBottom, d3.axisLeft.
Therefore, those lines should be:
var xAxis = d3.axisBottom(xRange).tickFormat(function(d){ return d.x;});
var yAxis = d3.axisLeft(yRange);
PS: I'm assuming that you want the ticks to be below the axis, which is normally the case, since you didn't show the orient in your original lines.
PPS: At the time of the writing the linked documentation applies to D3 v4. Caveat, lector: that can change at any time (see comments below).
I have the following dataset:
var data = [
{
"air_used": 0.660985,
"datestr": "2012-12-01 00:00:00",
"energy_used": 0.106402
},
{
"air_used": 0.824746,
"datestr": "2013-01-01 00:00:00",
"energy_used": 0.250462
} ...
]
And I want to draw a bar graph (for air_used) and line graph (for energy_used) that look like this:
My problem is that at the moment, with the x-scale I'm using, the graph looks like this - basically the bars are in the wrong position, and the last bar is falling off the chart:
Here is a JSFiddle with full code and working graph: http://jsfiddle.net/aWJtJ/4/
To achieve what I want, I think I need to amend the x-scale so that there is extra width before the first data point and after the last data point, and so that the bars are all shifted to the left by half the width of each bar.
Can anyone help me figure out what I need to do with the x-scale?
I've tried adding an extra month to the domain - that stops the last bar falling off the end of the graph, but it also adds an extra tick that I don't want, and it doesn't fix the position of the line graph and ticks.
If possible I want to continue to a time scale for the x-axis, rather than an ordinal scale, because I want to use D3's clever time-based tick formatters and date parsers, e.g. xAxis.ticks(d3.time.weeks, 2).
Expand your domain to be +1 and -1 month from the actual extent of your data. That will pad the graph with the extra months on either side and then update the bar width to add 2 to the count of data elements.
var barRawWidth = width / (data.length + 2);
See this fiddle: http://jsfiddle.net/reblace/aWJtJ/6/
If you want to hide the lower and upper boundary months, you can hack it like this: http://jsfiddle.net/reblace/aWJtJ/7/ by just adding and subtracting 20 days instead of a whole month, but there are probably more elegant ways to do it.
var xExtent = d3.extent(data, function(d) { return d.date; });
var nxExtent = [d3.time.day.offset(xExtent[0], -20), d3.time.day.offset(xExtent[1], 20)];
x.domain(nxExtent);
As pointed out in the comments, I think the best approach is to use d3.scale.ordinal. Note that using it doesn't prevent you from using d3.time parsers, but you need to take into account the bar width to align the line with the bars.
An example solution is here:
http://jsfiddle.net/jcollado/N8tuR/
Relevant code from the solution above is as follows:
// Map data set to dates to provide the whole domain information
var x = d3.scale.ordinal()
.domain(data.map(function(d) {
return d.date;
}))
.rangeRoundBands([0, width], 0.1);
...
// Use x.rangeBand() to align line with bars
var line = d3.svg.line()
.x(function(d) { return x(d.date) + x.rangeBand() / 2; })
.y(function(d) { return y(d.energy_used); });
...
// Use x.rangeBand() to set bar width
bars.enter().append("rect")
.attr("class", "air_used")
.attr("width", x.rangeBand())
...
Note that date parsing code has been moved up to have d.date available when creating the x scale. Aside from that, d3.time statements have not been modified at all.