Dimple.js line chart with composite axis, no links between points on series - d3.js

I've been playing with the newly released version 2.0.0 and I'm trying to replicate the composite axis example with some simple opinion poll data - 3 series (Yes,No,Unsure) against a single Y-axis.
I'm clearly making a basic error somewhere... the basic principle is you define one y-axis then pass that as a reference each time you want to layer on another set of data, but it doesn't appear to be treating them as three distinct series (there are no lines between points except in cases linking a couple of identical, but non-adjacent values, e.g. "No" 30% in 2002 and 2004, despite there being a different value in between).
Also, as per the commented-out line, I'm getting a D3 error if I try and switch the x-axis to Time instead.
JSFiddle live example
pollData = [
{ Year : 2001, Yes: 50, No: 40, Unsure: 10 },
{ Year : 2002, Yes: 60, No: 30, Unsure: 10 },
{ Year : 2003, Yes: 65, No: 25, Unsure: 5 },
{ Year : 2004, Yes: 75, No: 30, Unsure: 4 },
{ Year : 2005, Yes: 80, No: 10, Unsure: 5 }
];
var svg = dimple.newSvg("#chartContainer", 590, 400);
var myChart = new dimple.chart(svg, pollData);
myChart.setBounds(75, 30, 490, 330)
// Why won't a time axis working...? ("undefined is not a function" in D3)
// var dateAxis = myChart.addTimeAxis("x", "Year", "%Y");
var dateAxis = myChart.addCategoryAxis("x", "Year");
dateAxis.addOrderRule("Year");
var yesAxis = myChart.addMeasureAxis("y", "Yes");
var noAxis = myChart.addMeasureAxis(yesAxis, "No");
var unsureAxis = myChart.addMeasureAxis(yesAxis, "Unsure");
myChart.addSeries("Yes", dimple.plot.line, [dateAxis, yesAxis]);
myChart.addSeries("No", dimple.plot.line, [dateAxis, noAxis]);
myChart.addSeries("Unsure", dimple.plot.line, [dateAxis, unsureAxis]);
yesAxis.title = '%';
myChart.draw();

The issue here isn't the composite axes, which you are using correctly. It's the series definition. The first parameter of the addSeries either takes dimensions to disaggregate by or a field which is not in the data, which it uses as a label. The only limitation of this approach is that you cannot label your series by a dimension name in the data. In this case the first series for example (it applies to all), is disaggregated by "Yes" which for the line series means it tries to draw a line for each value of "Yes", meaning 1 line per row, hence all the single unconnected points. The fix is to just name your lines something different. For example:
myChart.addSeries("Y", dimple.plot.line, [dateAxis, yesAxis]);
myChart.addSeries("N", dimple.plot.line, [dateAxis, noAxis]);
myChart.addSeries("U", dimple.plot.line, [dateAxis, unsureAxis]);
Here's your updated fiddle: http://jsfiddle.net/RawyW/2/
If you want the names to look like they match you can just add a trailing space to the series names:
myChart.addSeries("Yes ", dimple.plot.line, [dateAxis, yesAxis]);
myChart.addSeries("No ", dimple.plot.line, [dateAxis, noAxis]);
myChart.addSeries("Unsure ", dimple.plot.line, [dateAxis, unsureAxis]);
This will also work

Related

D3 tickSizeOuter(0) to remove end-ticks NOT working

I'm following the example here to remove end ticks in my bar graph, as follows:
var yAxis = d3.axisLeft(yScale);
yAxis.tickValues([0, 500, 1000, 1500, 2000, 2500]);
yAxis.tickSizeOuter(0); // <--- This does not remove the end ticks
However, this does not remove the end ticks in the y-axis.
Now, when I enter this line of code:
yAxis.tickSize(0);
I am able to remove all the ticks on the y-axis.
Why isn't the tickSizeOuter(0) code working and how can I remove just the end-ticks?
Here is the version of d3 I am using with my Angular project:
"d3": "^5.9.1"
yAxis.tickSizeOuter(0) remove outer ticks which means that y line is straight line, but it doesn't remove actual ticks (e.g. 0-). If you what that you should use something like:
const tick_count = d3.selectAll("g.axis.axis--y .tick").size();
d3.selectAll("g.axis.axis--y .tick")
.each(function (d, i) {
console.log(d,i)
if ( i == 0 || i == tick_count-1) {
this.remove();
}
});
Hope I understand correctly your misunderstanding :)
Working solution for D3 v5.9.2:
After you declared yAxis and set the attributes, wrap it in a g and call it:
var yAxis = d3.axisLeft(yScale);
yAxis.tickValues([0, 500, 1000, 1500, 2000, 2500]);
svg.append("g").attr("id", "yAxisG").call(yAxis);
Remove the first <path> element from the group:
d3.select("g#yAxisG").select("path").remove();

How to remove weekends dates from x axis in dc js chart

I have data for every date from Jan 2018 and I am creating a stacked line chart out of that data. Every weekend the count of my data is zero, so every weekend it shows a dip in my graph (as data reaches to zero). I want to avoid that dip. I have a Date column as well as a Day column. The Day column has values from 1 to 7 representing each day of week (1 is Monday and 7 is Sunday). Can I modify my x axis or graph to show only weekdays data?
Fiddle
var data = [
{ Type: 'T', Date: "2018-01-01", DocCount: 10, Day: 1},
{ Type: 'E', Date: "2018-01-01", DocCount: 10, Day: 1},
...
]
chart
.height(350)
.width(450)
.margins({top: 10, right: 10, bottom: 5, left: 35})
.dimension(dateDim)
.group(tGroup)
.stack(eGroup, "E")
.valueAccessor( function(d) {
return d.value.count;
})
.transitionDuration(500)
.brushOn(false)
.elasticY(true)
.x(d3.time.scale().domain([minDateTime, maxDateTime]))
.xAxis().ticks(10).tickFormat(d3.format("s"));
A time scale is always going to be very literal about how it maps dates to x coordinates. It has no notion of "skipping dates".
Instead, I would suggest using an ordinal scale for this purpose. With an ordinal scale, you decide exactly what the input and output values will be. dc.js will also help you out by automatically determining the input (domain) values.
Tell the chart to use an ordinal scale like this:
chart
.x(d3.scale.ordinal())
.xUnits(dc.units.ordinal)
Remove any empty dates like this. remove_empty_bins is from the FAQ but I modified it to look at the count element.
function remove_empty_bins(source_group) {
return {
all:function () {
return source_group.all().filter(function(d) {
//return Math.abs(d.value) > 0.00001; // if using floating-point numbers
return d.value.count !== 0; // if integers only
});
}
};
}
var nz_tGroup = remove_empty_bins(tGroup),
nz_eGroup = remove_empty_bins(eGroup);
chart
.group(nz_tGroup)
.stack(nz_eGroup, "E")
Only question is, what if there is a weekday that happens not to have any data? Do you still want that to drop to zero? In that case I think you'd probably have to modify the filter in remove_empty_bins above.
Fork of your fiddle.

Plot.ly: Simple prefix or suffix for hover text

I've tried reading the documentation but I haven't found a simple way to add a suffix to a hover text.
Is there a way to add text to the hoverformat? For example y respondents where y is the y value.
The setting yaxis: {hoverformat: ''} does not seem to allow strings?
I'm used to working with FlotChart where you can simply put "%y respondents".
Any help is much appreciated.
If you set text in your trace you can get a suffix.
Sets text elements associated with each (x,y) pair. If a single
string, the same string appears over all the data points. If an array
of string, the items are mapped in order to the this trace's (x,y)
coordinates.
Together with hoverinfo (hoverinfo: 'text+y') you get close to what you what you want (except for the unneeded line break which would need to be removed manually).
For bar charts this does not seem to work (at least not in Jan 2017). It is necessary to replicate the data and write it into an array in the text attribute (which also solves the line break issue).
var N = 16,
x = Plotly.d3.range(N).map(Plotly.d3.random.normal(3, 1)),
y = Plotly.d3.range(N),
data = [{
x: x,
y: y,
type: 'scatter',
mode: 'markers',
text: 'respondents',
marker: {
size: 16
},
hoverinfo: 'text+y'
}],
layout = {
hovermode: 'closest',
};
Plotly.plot('myDiv', data, layout);
//here comes the bar chart
var data = [{
x: ['Jan', 'Feb', 'Mar'],
y: [20, 14, 25],
type: 'bar',
text: [],
hoverinfo: 'text'
}];
for (N = 0; N < data[0].y.length; N += 1) {
data[0].text.push(data[0].y[N] + ' Respondents');
}
Plotly.plot('myBarChart', data);
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<div id="myDiv" style="width:400px;height:400px;"></div>
<div id="myBarChart" style="width:400px;height:400px;"></div>

dc.js stacked line chart with more than 1 dimension

My dataset is an array of json of the like :
var data = [ { company: "A", date_round_1: "21/05/2002", round_1: 5, date_round_2: "21/05/2004", round_2: 20 },
...
{ company: "Z", date_round_1: "16/01/2004", round_1: 10, date_round_2: "20/12/2006", round_2: 45 }]
and I wish to display both 'round_1' and 'round_2' time series as stacked line charts.
The base line would look like this :
var fundsChart = dc.lineChart("#fundsChart");
var ndx = crossfilter(data);
var all = ndx.groupAll();
var date_1 = ndx.dimension(function(d){
return d3.time.year(d.date_round_1);
})
fundsChart
.renderArea(true)
.renderHorizontalGridLines(true)
.width(400)
.height(360)
.dimension(date_1)
.group(date_1.group().reduceSum(function(d) { return +d.round_1 }))
.x(d3.time.scale().domain([new Date(2000, 0, 1), new Date(2015, 0, 1)]))
I have tried using the stack method to add the series but the problem resides in the fact that only a single dimension can be passed as argument of the lineChart.
Can you think of a turnaround to display both series while still using a dc chart?
Are you going to be filtering on this chart? If not, just create a different group on a date_2 dimension and use that in the stack. Should work.
If you are going to be filtering, I think you'll have to change your data model a bit. You'll want to switch to have 1 record per round, so in this case you'll have 2 records for every 1 record you have now. There should be 1 date property (the date for that round), an amount property (the contents of round_x in the current structure), and a 'round' property (which would be '1', or '2', for example).
Then you need to create a date dimension and multiple groups on that dimension. The group will have a reduceSum function that looks something like:
var round1Group = dateDim.group().reduceSum(function(d) {
return d.round === '1' ? d.amount : 0;
});
So, what happens here is that we have a group that will only aggregate values from round 1. You'll create similar groups for round 2, etc. Then stack these groups in the dc.js chart.
Hopefully that helps!

How to handle duplicate values in d3.js

First I'm a d3.js noob :)
How you can see from the title I've got a problem with duplicated data and aggregate the values is no option, because the name represent different bus stops. In this example maybe the stops are on the fron side and the back side of a building.
And of course I like to show the names on the x-axis.
If i created an example and the result is a bloody mess, see jsFiddel.
x = index
name = bus stop name
n = value
I've got a json e.g.:
[{
"x": 0,
"name": "Corniche St / Abu Dhabi Police GHQ",
"n": 113
},
{
"x": 1,
"name": "Corniche St / Nation Towers",
"n": 116
},
{
"x": 2,
"name": "Zayed 1st St / Al Khalidiya Public Garden",
"n": 146
},
...
{
"x": 49,
"name": "Hamdan St / Tariq Bin Zeyad Mosque",
"n": 55
}]
The problem: It is possible that the name could appear more then once e.g.
{
"x": 1,
"name": "Corniche St / Nation Towers",
"n": 116
}
and
{
"x": 4,
"name": "Corniche St / Nation Towers",
"n": 105
}
I like to know is there a way to tell d3.js not to "delete" duplicated names and instead just show all names in sequence with their values.
Any ideas or suggestions are very welcome :) If you need more information let me know.
Thanks in advanced
Mario
Lars is right: the d3.ordinal scale is doing exactly what it should: treating duplicate values as repeat instances. See here for more details: https://github.com/mbostock/d3/wiki/Ordinal-Scales
You can use a regular linear scale instead, like this: http://jsfiddle.net/vy8vjy4r/2/
The changes are to make the scale linear and set the domain to be the length of your dataset.
var x = d3.scale.linear().domain([0,j_data.length]).range([0, width]),
When you pass a value to the scale, you simply pass the position in the list. I'm using the index - the i in function(d,i) - but you could have used the x in your dataset. (I didn't use it as it looks like you don't need it.)
.x(function (d,i) { return x(i); })
Hopefully this works for you.
Additional information on axis
Strictly speaking, I guess this should have been an additional question, but to get the text on the axis, you can simply add these two lines of code in where you modify the text in xAxisGroup, after .selectAll("text"):
.data(j_data.filter(function(d,i) { return !(i%5); }))
.text(function(d){ return d.name; })
The axis is displaying numbers every fifth item, so we choose every fifth item from the dataset. This gives us data that matches the existing labels, and we change the text to the .name value, see http://jsfiddle.net/vy8vjy4r/4/
This approach isn't particularly strong: it depends on D3 displaying every fifth stop, and for short or very long routes (or whatever these are) it might display all stops, or every tenth, etc. I would rather not use the D3 axis and build your own. For something like this, it shouldn't be too hard, although fitting all the names in might be hard in this space.
Try this: http://jsfiddle.net/vy8vjy4r/5/
Try this filter,
var names = [];
var result = [];
var indx=-1;
for(var i=0; i< j_data.length; i++){
indx = names.indexOf(j_data[i].name);
if(indx==-1){
names.push(j_data[i].name);
result.push(j_data[i]);
}
}
j_data= result;
Do this after your j_data array, it'll remove the duplicated objects from your j_data array. And see this http://jsfiddle.net/vy8vjy4r/1/
If it is not, what you are looking for, ask what change you need.

Resources