I have a dataset which consists of 5 columns -> country, id, value and sector. I was able to create a row chart in dc.js using the value and country, where country is my dimension.
var rowChart = dc.rowChart('#rowChart');
d3.csv('data.csv', function(data){
data.forEach(function(d){
d.country = d.country;
d.id = d.id;
d.value = +d.value;
d.sector = d.sector;
});
var height = 300;
var width = 300;
var ndx = crossfilter(data)
var countryDim = data.dimension(function (d) {
return d.country;
});
var countryGroup = countryDim.group().reduceSum(function (d) {
return d.value
})
rowChart
.width(300)
.height(900)
.margins({top: 10, right: 10, bottom: -1, left: 30})
.dimension(countryDim)
.group(countryGroup)
.colors('#86BC25')
.ordering(function (d) { return -d.value; })
.elasticX(true)
.xAxis();
rowChart
.title(function (d) { return d.value;})
.renderTitleLabel(true)
.titleLabelOffsetX(10);
dc.renderAll();
});
and this is my data in csv
country,id,value,sector
USA,0982,10,high
AUS,0983,9,high
IND,0982,10,high
CHN,0982,8,high
CUB,0986,5,middle
FIN,0987,low
i tried creating a jsfiddle, but does not seem to work, sorry my first time
http://jsfiddle.net/i8rice/2r76bjt7/4/
I want to be able to create two drop down with check boxes. One to filter the row chart by country and another by sector. So if I first filter the sector by 'high' in the drop down menu the row chart will get filtered and the other drop down menu should only show me the 5 'high' countries.
I know this is achievable using dc.selectMenu but I wan that drop down check box style. I was wondering if this is possible with dc.js?
Sorry I am very new to asking questions and in d3.js, dc.js and crossfilter.
Thanks to Gordon the check box within the drop down menu was working. However upon discussing with a few others, they have suggested that the check box, once ticked, is not calling the event handler, so wrote this, which is pretty much the same as the one within dc.js
selectField.on('postRender', function() {
$('#menuselect select').change(function(){
console.log($(this).val())
if ($(this).val() && $(this).val() != "") {
selectField.replaceFilter([$(this).val()]);
} else {
selectField.filterAll();
}
dc.events.trigger(function () {
dc.redrawAll();
});
}).multipleSelect({ placeholder: "Select Country"})
});
And everything worked, well, tested it on local. I don't know of any other ways as I am still new to this.
Related
I need to figure out this bug, my dc.js chart works just fine, except when I add negative values to the stacked bar-chart. Instead of starting from y=0 it starts from the top of the stacked one and messes up the chart.
function grid (selector,data) {
var ndx = crossfilter(data),
all = ndx.groupAll();
var bar_bank = dc.barChart(selector + " .bank");
var bank = ndx.dimension(function(d) {
if (typeof d.bank == "undefined") return "";
return d.bank;
});
var bankGroupBuy = bank.group().reduceSum(function(d) {
return d.buy; });
var bankGroup = bank.group().reduceSum(function(d) {
return d.sell); });
bar_bank
.width(444)
.height(300)
.outerPadding(22)
.gap(1)
.x(d3.scaleBand())
.y(d3.scaleLinear())
.xUnits(dc.units.ordinal)
.brushOn(false)
.elasticY(true)
.yAxisLabel("#BARCHART")
.dimension(bank)
.group(bankGroupBuy)
.stack(bankGroupSell)
;
dc.renderAll();
}
This renders:
but when i
return -d.sell
This happens:
Any ideas how I can fix it? So the stacked negative one starts from 0 and goes down, and does not start from the top of the first one?
Thanks in advance!
This is my csv:
time,bank,buy,sell,AVG_vol_price
2019-02-11,AVA,26378,138177,1.688
2019-02-11,NON,19340,13500,1.683
2019-02-11,SFK,0,43,1.74
2019-02-11,SHB,11300,498,1.692
2019-02-11,SWB,101200,6000,1.689
2019-02-12,AVA,125612,138612,1.683
2019-02-12,ENS,5000,0,1.702
perhaps the answer is very obvious and has nothing to do the libraries but with general javascript, JQuery or Ajax. I am very new to javascript and I was trying to implement a dashboard using flask as the backend.
Crossfilter and dc help me select ranges on the charts and see how that affects the whole dataset. If I add a:
<span class="filter"></span>
It will display the range above the chart
But that is a class "filter" inside a span object and not a variable or data that I can get inside the code. Below is what I use to display the chart (btw, the reset button does not appear at all)
<div class='span6' id='dc-close-chart'>
<h4>
close
</h4>
<p>range:
<span class="filter">
<a class="reset" href="javascript:closeChart.filterAll();dc.redrawAll();" style="display: none;">
reset
</a>
</span>
</p>
</div>
I would like to be able to do the following:
Be able to access that range and store it is a variable so I can access it and maybe post it using a submit button.
Be able to replace that label for an input textbox to modify the range and change the filter accordingly.
I've been looking around the crossfilter and dc.js forums but I didn't find anything relevant, what I want to do, is it even possible?
Below the JS code, can I create a variable that captures that?
var closeChart = dc.barChart("#dc-close-chart");
// set constants
var n_bins = 35;
d3.csv("/static/data2.csv", function (data) {
console.log(data);
data.forEach(function (d) {
d.close = d3.round(+d.close, 1);
});
// Run the data through crossfilter and load our 'facts'
var facts = crossfilter(data);
var all = facts.groupAll();
// count all the facts
dc.dataCount(".dc-data-count")
.dimension(facts)
.group(all);
// for Each chart numeric
var closeValue = facts.dimension(function (d) {
return d.close; // add the magnitude dimension
});
var closeValueGroupSum = closeValue.group()
.reduceSum(function (d) {
return d.close;
}); // sums
var closeValueGroupCount = closeValue.group()
.reduceCount(function (d) {
return d.close;
}) // counts
// extent
var closeExtent = d3.extent(data, function (d) {
return d.close;
});
// binwidth
var closebinWidth = (closeExtent[1] - closeExtent[0]) / n_bins;
//group
var closeGroup = closeValue.group(function (d) {
return Math.floor(d / closebinWidth) * closebinWidth;
});
// Setup the charts
// Magnitide Bar Graph Counted
closeChart.width(480)
.height(150)
.margins({
top: 10,
right: 10,
bottom: 20,
left: 40
})
.dimension(closeValue)
.group(closeGroup)
.transitionDuration(500)
.centerBar(true)
.gap(1) // 65 = norm
// .filter([3, 5])
.x(d3.scale.linear().domain(closeExtent).range([0, n_bins]))
.elasticY(true)
.xUnits(function () {
return n_bins;
})
.controlsUseVisibility(true)
.colors(['LimeGreen'])
.xAxis().tickFormat(function (v) {
return v;
});
// Render the Charts
dc.renderAll();
});
You can read the currently active filters using chart.filter() or chart.filters().
There isn't anything built in to parse filters from text, but if you figure out how to do that, you could apply the filter with
chart.replaceFilter([dc.filters.RangedFilter(min, max)])
RangedFilter docs.
replaceFilter docs.
I'm trying to recreate the single select bar on a dc.js composite chart as shown here
https://dc-js.github.io/dc.js/examples/bar-single-select.html
I've tried adding a filter handler to the child chart but it never gets called when I click on the bar. I've also tried adding a filter handler to the Composite chart itself with no luck. Is there any way I can select a bar on a composite chart or do I have to assign it a colour and then color the other bars grey manually and redraw the graph based on what was clicked?
This is the initialization of the graph in my component.
The data goes through a formatting process where I parse the date using the formatData function. I also pass in a dimensions prop (apologies for the bad naming) which tells the component what kind of chart should correspond to the chart name and the color of the dataset.
dimensions={
{"Data1": ["line", AppStyles.color.warning],
"Data2": ["line", AppStyles.color.danger],
"Data3": ["bar", AppStyles.color.blue]
}
}
formatData = (data) => {
let formattedData = [];
for(let key in data) {
formattedData.push({
...data[key],
x: this.parseDate.parse(data[key].x)
})
}
return formattedData;
}
componentDidMount(){
let data = this.formatData(this.props.data);
this.ndx = crossfilter.crossfilter(data);
this.chart = dc.compositeChart(this.multiLineChartContainer);
this.dimension = this.ndx.dimension((d) => {
return d.x;
});
let minDate = this.dimension.bottom(1)[0].x;
let maxDate = this.dimension.top(1)[0].x;
let composeGroup = [];
Object.keys(this.props.dimensions).map((dim,i) => {
let grp = this.dimension.group().reduceSum((d) => {
return d[dim];
});
if(this.props.dimensions[dim][0] === "bar"){
composeGroup.push(dc.barChart(this.multiLineChartContainer)
.group(grp, dim)
.colors("blue")
.centerBar(true)
.addFilterHandler(function(filters, filter) {return [filter];})
)
} else {
composeGroup.push(dc.lineChart(this.multiLineChartContainer)
.group(grp, dim)
.colors(this.props.dimensions[dim][1])
.useRightYAxis(true)
);
}
});
this.chart.width(this.props.width)
.height(this.props.height)
.renderHorizontalGridLines(true)
.x(d3.time.scale().domain([minDate, maxDate]))
.elasticY(true)
.elasticX(true)
.xAxisLabel("Cohort")
.brushOn(false)
.yAxisLabel("Left")
.rightYAxisLabel("Right")
.xUnits(()=>{
return 30;
})
.legend(dc.legend().x(this.chart.width()- 130))
.compose(composeGroup)
this.chart.renderlet((chart) => {
chart.selectAll('circle, rect.bar').on("click", (event) => {
this.props.dataSelect(event);
});
});
this.chart.xAxis().ticks(5)
this.chart.render();
}
Please consider adding your code (or better, a running example) next time you ask a question on SO.
It would also help to spell out what "no luck" means - wrong click behavior? No chart displayed at all?
It's hard to guess what might be going wrong for you.
This works fine for me, although ordinal scales are a little bit tricky, and composing them in a composite chart even more so.
Is the problem that you were not using an ordinal scale? Because currently the kind of selection (brush or click) is determined by the scale/xUnits and it's hard to get around it.
composite
.width(768)
.height(480)
.x(d3.scaleOrdinal().domain(d3.range(1,21)))
.xUnits(dc.units.ordinal)
.yAxisLabel("The Y Axis")
.legend(dc.legend().x(80).y(20).itemHeight(13).gap(5))
.brushOn(true)
.renderHorizontalGridLines(true)
.compose([
dc.barChart(composite)
.dimension(dim)
.colors('blue')
.group(grp2, "Bars")
.addFilterHandler(function(filters, filter) {return [filter];})
.centerBar(true),
dc.lineChart(composite)
.dimension(dim)
.colors('red')
.group(grp1, "Dots")
.dashStyle([2,2])
])
.render();
https://jsfiddle.net/gordonwoodhull/ronqfyj0/39/
I implemented a composite chart with two bar charts in which one bar chart consists of bars with different colored bars.
Now, I want to create a custom legend that represents each color bar (similar to https://dc-js.github.io/dc.js/examples/pie-external-labels.html used for pie chart).
Below is the code snippet of what I've done so far:
var buttonPress = dc.barChart(composite)
.dimension(joyTimeDimension)
//.renderlet(colorRenderlet)
//.colors('red')
.colors(colorbrewer.Set1[5])
.colorDomain([101, 105])
.colorAccessor(function (d) {
return d.value;
})
.group(btnGroup, "Button Press")
.keyAccessor(function(d) {return d.key[0];})
.valueAccessor(function (d) {
return d.value;
})
.title( function(d){
return [
"Time: "+d.key[0],
"button Name: "+d.key[1],
"button: "+ d.value
].join('\n')
});
var joyStick = dc.barChart(composite)
.dimension(joyTimeDimension)
.colors('blue')
.group(stepperGroup,"Joy Stick Movement")
.keyAccessor(function(d) {return d.key[0];})
.title( function(d){
return [
"Time: "+d.key[0],
"Stepper Position: "+ d.value
].join('\n')
});
composite
.width(1200)
.transitionDuration(500)
.margins({top: 30, right: 50, bottom: 25, left: 40})
.x(d3.time.scale().domain([startDate,currDate]))
.xUnits(function(){return 150;})
//.xUnits(d3.time.second)
.elasticY(true)
.legend(dc.legend().x(1000).y(4).itemHeight(13).gap(5))
.renderHorizontalGridLines(true)
.renderTitle(true)
.shareTitle(false)
.compose([buttonPress, joyStick])
.brushOn(false)
Is there a way to create a custom legend for this scenario?
Thanks in advance.
Let me provide a little bit of background about how the legend is built.
The legend in dc.js is really not all that sophisticated. It just calls .legendables() on the chart, and the chart decides what items to display in the legend.
Each chart has its own special-purpose code for this.
If we look at the source for compositeChart.legendables(), it's just recursively getting the legendables for each child chart and concatenating them:
_chart.legendables = function () {
return _children.reduce(function (items, child) {
if (_shareColors) {
child.colors(_chart.colors());
}
items.push.apply(items, child.legendables());
return items;
}, []);
};
The pie chart creates a legendable for each pie slice:
_chart.legendables = function () {
return _chart.data().map(function (d, i) {
var legendable = {name: d.key, data: d.value, others: d.others, chart: _chart};
legendable.color = _chart.getColor(d, i);
return legendable;
});
};
The legendables for the bar chart come from the stack mixin, which creates a legendable for each stack:
_chart.legendables = function () {
return _stack.map(function (layer, i) {
return {
chart: _chart,
name: layer.name,
hidden: layer.hidden || false,
color: _chart.getColor.call(layer, layer.values, i)
};
});
};
Given that there's currently no way to get a bar chart to display a pie chart's legend, I think the easiest thing to do is override legendables for your bar chart with its custom colors:
buttonPress.legendables = function() {
return btnGroup.all().map(function(kv) {
return {
chart: buttonPress,
// display the value as the text (not sure what you want here)
name: kv.value,
// apply the chart's color scale to get the color
color: buttonPress.colors()(kv.value)
};
})
};
There are probably some more details to be worked out, such as what if the same value occurs twice? I am assuming you can just read the input data from the group and .map() it, but you might need to generate your data a different way.
But this should give the general idea. Lmk if it doesn't work and I'll be glad to follow up.
I want to filter data in the table based on the age and height at the same time using 2 range sliders.
I have implemented 2 range sliders (Age and Height) using d3.slider.js and a dc.dataTable. I want to use these 2 range sliders at the same time, but it seems that they are not working properly.
Also, under the table, there is the text "49 selected out of 49 records". The numbers are not changing while using the sliders.
Code:
var dataTable = dc.dataTable("table#list");
var dispatch = d3.dispatch('load','filter');
d3.json('data.json',function(json){
dispatch.load(json)
});
dispatch.on('load',function(json) {
var formatNumber = d3.format( ",d");
var facts = crossfilter(json);
var dimensionAge = facts.dimension(function(d) {
return +d.age;
});
var accessorAge = function(d) {
return d.age;
};
var dimensionHeight = facts.dimension(function(d) {
return +d.height;
});
var accessorHeight = function(d) {
return d.height;
};
var range = d3.extent(json, accessorAge);
var range2 = d3.extent(json, accessorHeight);
var all = facts.groupAll();
d3.select("div#slider3")
.call(d3.slider().axis(true).min(range[0]).max(range[1]).value(range)
.on("slide", function(evt,value) {
dispatch.filter(value);
d3.select("#slider3textmin").text(Math.floor(value[0]));
d3.select("#slider3textmax").text(Math.floor(value[1]))
}))
d3.select("div#slider4")
.call(d3.slider().axis(true).min(range2[0]).max(range2[1]).value(range2)
.on("slide", function(evt,value) {
dispatch.filter(value);
d3.select("#slider4textmin").text(Math.floor(value[0]));
d3.select("#slider4textmax").text(Math.floor(value[1]))
}))
FieldNames = [
"",
"Age",
"Weight",
"Height",
"Eye Color",
"Hair Color",
"Race",
"Sex",
"Annual Income"
];
d3.select("tr#FieldNames").selectAll("th")
.data(FieldNames)
.enter()
.append("th")
.append("text")
.text(function(d){
return d;
});
dataTable
.dimension(dimensionAge)
.group(function(d) {
return d.sex;
})
.columns([
function(d) {return "";},
function(d) {return d.age;},
function(d) {return d.weight;},
function(d) {return d.height;},
function(d) {return d.eyeColor;},
function(d) {return d.hairColor;},
function(d) {return d.race;},
function(d) {return d.sex;},
function(d) {return formatNumber(d.annualIncome);}
]);
dispatch.on('filter',function(value){
dataTable.replaceFilter(dc.filters.RangedFilter(value[0], value[1]));
dataTable.redraw();
})
dc.dataCount(".dc-data-count")
.dimension(facts)
.group(all);
dc.renderAll();
});
Link to the website
Plunker
Original response on the dc.js users group.
Nice use of d3.slider.js - I haven't seen that used with dc.js before.
At a quick glance, I see two problems here. First, you're using one
dispatch for both sliders, so both sliders are filtering the age,
since that's the dimension of the table. You'd probably want to create
another dimension for filtering by height, and you don't really need
to attach that to a chart.
Second, instead of just redrawing the chart with dataTable.redraw(),
you probably want to call dataTable.redrawGroup() so that all charts
in its chart group get redrawn, including the dataCount.
Specifically:
you'll need two filter events in your dispatch
var dispatch = d3.dispatch('load','filterAge','filterHeight');
the age slider will call filterAge
dispatch.filterAge(value);
and the height slider will call filterHeight
dispatch.filterHeight(value);
the current filter event handler will now handle filterAge and it will call redrawGroup
dispatch.on('filterAge',function(value){
dataTable.replaceFilter(dc.filters.RangedFilter(value[0], value[1]));
dataTable.redrawGroup();
})
we add another filterHeight handler which directly filters dimensionHeight and also redraws the chart group
dispatch.on('filterHeight',function(value){
dimensionHeight.filter([value[0], value[1]]);
dataTable.redrawGroup();
})
Reset All will also have to clear dimensionHeight. (Since this dimension isn't used by any chart, dc.filterAll() won't find it.)
Reset All
Fork of your plunker.
this for reset all, the 49 selected out of 49 records already change correcly
replace this
Reset All
to this
Reset All
add this after dispatch on load
dispatch.on('load',function(json) {
//your code
})
function sololo(){
//table
dispatch.filterAge([0,100]);
dispatch.filterHeight([0,100]);
//text slider
d3.select("#slider4textmin").text(0)
d3.select("#slider4textmax").text(0)
d3.select("#slider3textmin").text(0);
d3.select("#slider3textmax").text(0)
//slider
d3.select('#slider3').select('#handle-one').style('left','0%')
d3.select('#slider3').select('#handle-two') .style('right','0%')
d3.select('#slider3').select('div').style('left','0%').style('right','0%')
d3.select('#slider4').select('#handle-one').style('left','0%')
d3.select('#slider4').select('#handle-two') .style('right','0%')
d3.select('#slider4').select('div').style('left','0%').style('right','0%')
}