How can I color my scatter using a vector with values? - dc.js

Well, I want to color my scatter using a vector with values. Actually, I want to use other dimension than the one used for creating the scatter.
Using these lines it gives a color to my scatter using the values given by the dimension that scatter is built on.
.colorAccessor(function(d) {return d.key[1]})
.colors(d3.scaleSequential(d3.interpolateOranges))
.colorDomain(y_range)
y_range = [y_min, y_max]
I tried to include the column for color in the dimension of the scatter, but it slows down the process of filtering. Something like this:
scatterDim = crossFilter.dimension(function(d) { return [d[it.variable[0]], d[it.variable[1]], d[it.color]]})
.colorAccessor(function(d) {return d.key[2]})
.colors(d3.scaleSequential(d3.interpolatePlasma))
.colorDomain([colorUnits[0], colorUnits[colorUnits.length - 1]]),
I want to have a different dimension for color:
colorDimension = crossFilter.dimension(function (d) { return d[it.color] }),
colorGroup = colorDimension.group().reduceCount(),
colorAll = colorGroup.all(),
colorUnits = [],
count = 0;
for(var color in colorAll)
{
colorUnits[count] = colorAll[color].key;
count++;
}
.colorAccessor(//some different code for my vector colorUnits or even for dimension?!//)
.colors(d3.scaleSequential(d3.interpolatePlasma))
.colorDomain([colorUnits[0], colorUnits[colorUnits.length - 1]]),
I would also like to know how to use scaleOrdinal for color. In case that the vector colorUnits contains strings.

The name "dimension" is a little confusing in crossfilter and dc.js. It isn't used to describe the "Y" (aggregated) values, or the color.
It really means, "I want to bin my data by this key, and filter on it."
The reason you will find color as a third element in dimension keys in many examples is that it's expedient. It's easier to change the keys than the aggregated values. But it doesn't really make sense.
The fact that your chart got slower when you added color to your dimension key tells me that you don't have a unique color for each X/Y pair. Instead of drawing a dot for each X/Y pair, you end up with a dot for each X/Y/color triplet.
You also don't need to create a separate color dimension unless you want to bin, aggregate, or filter on color.
Assuming you only want one dot per X/Y pair, you need to decide which color to use. Then you can change the reduction, instead of the key, to add this data:
scatterDim = crossFilter.dimension(function(d) {
return [d[it.variable[0]], d[it.variable[1]]];
}),
scatterGroup = scatterDim.group().reduce(
function(p, v) { // add
p.count++; // reduceCount equivalent
p.color = combine_colors(p.color, v[it.color]);
return p;
},
function(p, v) { // remove
p.count--;
// maybe adjust p.color
return p;
},
function() { // init
return {count: 0, color: null};
}
);
If you don't care which of the colors is used, you don't need combine_colors; just use v[it.color]. Otherwise, that's something you need to decide based on your application.
Now the scatter group has objects as its values, and you can change the scatter plot to take advantage of them:
scatterPlot
.existenceAccessor(d => d.value.count) // don't draw dot when it is zero
.colorAccessor(d => d.value.color)
If in fact you do want to draw all the dots with different colors, for example using opacity to allow overplotting, you probably need a canvas implementation of a scatter plot, because SVG is only good up to thousands of points. There is one in the works for dc.js but it needs to be ported to the latest APIs.
I would also like to know how to use scaleOrdinal for color. In case that the vector colorUnits contains strings.
Not sure what you mean here. scaleOrdinal takes strings as its domain, so
.colors(d3.scaleOrdinal(colorUnits, output_colors))
should work?
Example
Since I'm failing to communicate something or another, here is an example. The color strings come from an array since I don't have an example of your data or code:
const names = ["Zero", "One", "Two", "Three", "Four", "Five"];
speedSumGroup = runDimension.group()
.reduce(
function(p, v) { // add
p.count++; // reduceCount equivalent
p.color = names[+v.Expt];
return p;
},
// ... as before
);
chart
.colorAccessor(d => d.value.color)
.colors(d3.scaleOrdinal(names, d3.schemeCategory10))
Once again, if the method isn't working for you, the best way to figure it out is to log speedSumGroup.all(). I get:
[
{
"key": [
1,
850
],
"value": {
"count": 1,
"color": "One"
}
},
{
"key": [
1,
880
],
"value": {
"count": 1,
"color": "Three"
}
},
{
"key": [
1,
890
],
"value": {
"count": 2,
"color": "Five"
}
},
// ...
]
Example fiddle.

Related

Legend in deck.gl

I am wondering if it is possible to get access to aggregated data from a deck.gl layer to be able to draw a legend.
Because the colour scheme is supplied I would only require the extent of the aggregated values calculated by the screengrid layer to be able to add this to the legend.
I know there are tooltips, but in some circumstances it would be nice to have access to these values.
I'm using the HexagonLayer and for that one you can find the values for the layers by using a semi custom onSetColorDomain function when initializing your layer. Then save the domain range array to a variable and call a make legend function.
for example:
const hexlayer = new HexagonLayer({
id: 'heatmap',
pickable: true,
colorRange: COLOR_RANGE,
data: feats_obj.coords_array,
elevationScale: 9,
extruded: true,
radius: 300,
getColorValue: points => {
if( points.length > max_points) {
max_points = points.length
}
renderCategories( incident_categories )
return points.length
},
onSetColorDomain: (ecol) => {
console.log('color domain set', ecol)
max_span.innerHTML = ecol[1]
color_domain = ecol;
create_legend()
// console.log('max_points: ', max_points)
},...
})
The hacky way i figured out was to make a max points in a polygon global variable outside initializing the layer and have it update the value anytime there's a polygon with more points in it than that max value. An example of it in the wild is: https://mpmckenna8.github.io/sfviz/?start_date=2020-05-01&end_date=2020-05-31 with a link to the repo u can hack on there.

Crossfilter stacked bar charts negate values

I am using a crossfilter2 with dcv3
My data is in a csv which i loaded into memory
Original Data
Day, ID
1, 2
1, 2
1, 2
2, 5
3, 6
4, 6
Processed data
Day, ID, target
1, 2, True
1, 2, True
1, 2, True
2, 5, False
3, 6, False
4, 6, False
Currently what i am trying to do is create a crossfilter stackedbar chart with 2 bars. If ID == 2, i consider it as one group, and ID !=2 as another group. However, i cannot do it dynamically it in DC/crossfilter which results me having to preprocess the data to add a new column and work off the column as shown by my solution below.
Is there a better way?
var dimID = ndx.dimension(function(d) { return d.day; });
var id_stacked = dimID.group().reduce(
function reduceAdd(p, v) {
p[v.target] = (p[v.target] || 0) + 1;
return p;
},
function reduceRemove(p, v) {
p[v.target] = (p[v.target] || 0) - 1;
return p;
},
function reduceInitial() {
return {};
});
//Doing the stacked bar chart here
stackedBarChart.width(1500)
.height(150)
.margins({top: 10, right: 10, bottom: 50, left: 40})
.dimension(dimID)
.group(id_stacked, 'Others', sel_stack("True"))
.stack(id_stacked, 'Eeid of interest', sel_stack("False"))
This is my sel_stack function
function sel_stack(i) {
return function(d) {
return d.value[i] ? d.value[i] : 0;
};
}
I am plotting a bar chart with x-axis being the day and the Y-axis being the frequency of ID == 2 or ID!=2 in a stacked bar chart
So you want to group by day and then stack by whether ID===2. Although dc.js will accept many different formats, often the trick is getting the data into the right shape.
You're on the right track, but you don't need the extra column in order to create stacks for "is 2" and "not 2". You can calculate it directly:
var dayDimension = ndx.dimension(function(d) { return d.Day; }),
idStackGroup = dayDimension.group().reduce(
function add(p, v) {
++p[v.ID===2 ? 'is2' : 'not2'];
return p;
},
function remove(p, v) {
--p[v.ID===2 ? 'is2' : 'not2'];
return p;
},
function init() {
return {is2: 0, not2: 0};
});
These are standard add/remove functions for reducing multiple values for each bin. You'll find other variations where the name of the field is driven by the data. But here we know what fields will exist, so we can initialize them to zero in init and not worry about encountering new fields.
The add function is called when a row is added to the crossfilter or a filter changes so that a row is included; the remove function is called whenever a row is filtered out or removed from crossfilter. Since we're not worried about undefined (1) we can simply increment (++) and decrement (--) the values.
Finally we need accessors to pull these values out of the object. I think it's simpler to put the stack accessors inline - sel_stack was written for adding a dynamic number of stacks. (YMMV)
.group(idStackGroup, 'Others', d => d.value.not2)
.stack(idStackGroup, 'Eeid of interest', d => d.value.is2);
https://jsfiddle.net/gordonwoodhull/fu4w96Lh/23/
(1) If you do any arithmetic on undefined it casts to NaN and NaN ruins all further calculations.

Draw/update circles in rect - nested selections confusion

I am trying to get a layout that would, for each object in the data array:
append a rect or a g element that will serve as container
inside or on top of this, append a circle for each of the coordinates.
Below is a mock-up of how the data is massaged before I'm trying to append to the DOM (at the top of the update() function in the block below):
[{
label: 'foo',
circles: [
{ x: 0, y: 10 },
{ x: 10, y: 10 }
]
},{
...
}]
The drawing and updating of the rect elements seems to be working fine, but I am getting the selection and joins confused for the circles.
Here's the block: http://blockbuilder.org/basilesimon/91f75ab5209a62981f11d30a81f618b5
With
var dots = rects.selectAll('.dots')
I can select the right data below but can't draw it.
Could you help me getting the selections right so I can draw and update both the rect and the circle, please?
Thank you Gerard for your help. This is my current state, but I've pitted myself into a hole by running a for loop instead of d3 selections.
I wonder if I couldn't nest the circles in g elements after building a new data object like so:
var data = dataset.map(function(d) {
var circles = d3.range(d.amount).map(function(i) {
return {
x: (i % 5)*20,
y: (i / 5 >> 0)*20
}
});
return {
label: d.label,
dots: circles
};
});
From each object in data, we'll append a g, and inside each g we'll append the circles. Any help appreciated, since this will affect the dots + i used by the update pattern...
New question here
Here is the problem:
var dots = svg.selectAll('dots')
You're selecting something that doesn't exist. Because of that, your "enter" selection will always contain all the data, and your "exit" selection will always be empty.
The solution is changing it for something like this:
var dots = svg.selectAll(".dots" + i)
And, in the enter selection, setting the classes:
.attr("class", "dots" + i)
Here is your updated bl.ocks (with some other minor changes): https://bl.ocks.org/anonymous/4c2e1d66f1ab890da983465a4f84ca9b

pentaho CDE conditional formatting of bubble chart

I have used CCC Heat Grid in CDE to create a bubble chart with bubbles of different colors. My data set has only 6 values: (1, 1.1, 2, 2.1, 3, 3.1). I have sizeRole property to "value" so that the size of the bubble varies based on the magnitude of these six values. Alternative, I could have set colorRole property to "value". I have set three colors: green (1), yellow (2) and red (3).
Now, what I want to have 1 as green, 2 as yellow and 3 as red; and biggest constant size for 1.1, 2.1 and 3.1. The values 1.1, 2.1 and 3.1 represent alarms in my data set, so I want them to be of biggest size bubble or some other differentiating visual element.
I tried the following in pre-execution but no luck
function changeBubbles(){
var cccOptions = this.chartDefinition;
// For changing extension points, a little more work is required:
var eps = Dashboards.propertiesArrayToObject(cccOptions.extensionPoints);
// add extension points:
eps.bar_shape = function getShape(){
var val = this.scene.vars.value.value;
if(val == 1.1 || val == 2.1 || val == 3.1){
return 'cross';
}
else {}
};
// Serialize back eps into cccOptions
cccOptions.extensionPoints = Dashboards.objectToPropertiesArray(eps);
}
How can we achieve this?
I hope the answer is still relevant, given that this is a late response.
To use bubbles you should have useShapes: true.
You can set a different constant shape by using the shape option. For example, shape: "cross".
To have the bubble size be constant, you should set the "sizeRole" to null: sizeRole: null. Bubbles will take all of the available "cell" size.
Then, the "value" column should be picked up by the "colorRole", but to be explicit, specify: colorRole: "value".
By default, because the color role will be bound to a continuous dimension ("value"), the color scale will be continuous as well.
To make it a discrete scale, change the "value" dimension to be discrete:
dimensions: {
"value": {isDiscrete: true}
}
Finally, to ensure that the colors are mapped to the desired values, specify the "colorMap" option:
colorMap: {
"1": "green",
"2": "yellow",
"3": "red"
}
That's it. I hope this just works :-)

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