Dynamically add or remove dimensions with D3js parallel sets - javascript

I'm trying to change the dimensions of my parallel sets chart dynamically, as previously asked in this question.
I'm using the reusable chart created by Jason Davies:
http://www.jasondavies.com/parallel-sets/
I create my chart using this code:
var chart = d3.parsets()
.dimensions(["Survived", "Sex", "Age", "Class"]);
var vis = d3.select("#vis").append("svg")
.attr("width", chart.width())
.attr("height", chart.height());
d3.csv("titanic.csv", function(error, csv) {
vis.datum(csv).call(chart);
});
I have created a button that when clicked should call a function change that should change the dimensions to ["Survived", "Sex", "Class"].
The function:
function change() {
vis.call(chart.dimensions(["Survived", "Sex", "Class"]));
}
When the function is called I get the error Uncaught TypeError: Cannot read property 'hasOwnProperty' of undefined on line 90 which is within the updateDimensions function in the reusable chart.
Anyone has a solution for this or should I solve it by using the workaround by creating a new svg object with the new dimensions and remove the old one as user1386906 mentions in a comment in the previously asked question?

Is there a way to make the parallel set take different values which are in json for a data entity
I have did with some random number generator
svg.datum(data)
.call(chart
.value(function(d, i) {
var v1, v2, v3;
v1 = d["Valu"];
v2 = d["Val"];enter code here
v3 = d["Value"];
console.log(v1);
console.log(v2);
console.log(v3);
var x = Math.floor((Math.random() * 10) + 1);
if (x < 5) {
return v2;
} else {
return v3;
}
})
.mapLevel(levelMap)
.dimensions(arrayOfDimensions)
//dimensions to be displayed by chart. (i.e "Cost Item", "Location" etc)
);
There is three different values named, Value, val, valu , I want nodes to take different values for parallel nodes , So that each set of parallel node has different size of nodes
example :
nodeP node Parallel to nodeP
] ]
]
has different size so ribbon connecting them has varying size from source node to destination node.

Related

HighChart with more than 50 data points breaks

I am trying to create a bubble chart using the JS HighChart in Angular2+. Whenever there are more than 50 data points (bubbles), the graph breaks. There are the correct number of bubbles in the correct positions (x,y plots) with all different colors but the sizes are all the same even though the z-values are all different. (I am outputing the z-values in a tooltip and the z-values are accurate)
This function is how I am passing in data to the high-chart configuration.
setSeries() {
this.objData = []
this.Data.forEach(element => {
var x= element['xVal'];
var y = element['yVal'];
var z = element['zVal'].toFixed(0);
var name = element['seriesName'].trim();
var newData =[{
x:x,
y:y,
z:+z,
}]
// SetSeriesData is how i am creating the obj to pass into series=[] in highchart configuration
if(i<50) //If I comment this condition, the graph breaks. Right now, the graph is working properly
this.setSeriesData(sumData, name, this.objData)
i++
})
this.options.series = this.objData;
this.generateChart();
}
This is my setSeriesData function.
setSeriesData(graphData: any, dataName: any, objData: any){
var obj = {};
obj['name'] = dataName;
obj['data'] = graphData;
obj['events'] = {click: function(e) {
//takes me to another link
}};
objData.push(obj)
}
In the above function, I configured the chart so that when you click the bubble, it takes you to another page. When the data points >50, this click functionality is not working either. In addition, the fillOpacity is not correct.
Just a few things to point out
1. I am using Angular 2+
2. The discovered issues are, fillOpacity, click, and size based on z-value.
3. It works perfectly when the data points are less than 50
How can I fix this?

dc.js Scatter Plot with multiple values for a single key

We have scatter plots working great in our dashboard, but we have been thrown a curve ball. We have a new dataset that provides multiple y values for a single key. We have other datasets were this occurs but we had flatten the data first, but we do not want to flatten this dataset.
The scatter plot should us the uid for the x-axis and each value in the inj field for the y-axis values. The inj field will always be an array of numbers, but each row could have 1 .. n values in the array.
var data = [
{"uid":1, "actions": {"inj":[2,4,10], "img":[10,15,25], "res":[15,19,37]},
{"uid":2, "actions": {"inj":[5,8,15], "img":[5,8,12], "res":[33, 45,57]}
{"uid":3, "actions": {"inj":[9], "img":[2], "res":[29]}
];
We can define the dimension and group to plot the first value from the inj field.
var ndx = crossfilter(data);
var spDim = ndx.dimension(function(d){ return [d.uid, d.actions.inj[0]];});
var spGrp = spDim.group();
But are there any suggestions on how to define the scatter plot to handle multiple y values for each x value?
Here is a jsfiddle example showing how I can display the first element or the last element. But how can I show all elements of the array?
--- Additional Information ---
Above is just a simple example to demonstrate a requirement. We have developed a dynamic data explorer that is fully data driven. Currently the datasets being used are protected. We will be adding a public dataset soon to show off the various features. Below are a couple of images.
I have hidden some legends. For the Scatter Plot we added a vertical only brush that is enabled when pressing the "Selection" button. The notes section is populated on scatter plot chart initialization with the overall dataset statistics. Then when any filter is performed the notes section is updated with statistics of just the filtered data.
The field selection tree displays the metadata for the selected dataset. The user can decide which fields to show as charts and in datatables (not shown). Currently for the dataset shown we only have 89 available fields, but for another dataset there are 530 fields the user can mix and match.
I have not shown the various tabs below the charts DIV that hold several datatables with the actual data.
The metadata has several fields that are defined to help use dynamically build the explorer dashboard.
I warned you the code would not be pretty! You will probably be happier if you can flatten your data, but it's possible to make this work.
We can first aggregate all the injs within each uid, by filtering by the rows in the data and aggregating by uid. In the reduction we count the instances of each inj value:
uidDimension = ndx.dimension(function (d) {
return +d.uid;
}),
uidGroup = uidDimension.group().reduce(
function(p, v) { // add
v.actions.inj.forEach(function(i) {
p.inj[i] = (p.inj[i] || 0) + 1;
});
return p;
},
function(p, v) { // remove
v.actions.inj.forEach(function(i) {
p.inj[i] = p.inj[i] - 1;
if(!p.inj[i])
delete p.inj[i];
});
return p;
},
function() { // init
return {inj: {}};
}
);
uidDimension = ndx.dimension(function (d) {
return +d.uid;
}),
uidGroup = uidDimension.group().reduce(
function(p, v) { // add
v.actions.inj.forEach(function(i) {
p.inj[i] = (p.inj[i] || 0) + 1;
});
return p;
},
function(p, v) { // remove
v.actions.inj.forEach(function(i) {
p.inj[i] = p.inj[i] - 1;
if(!p.inj[i])
delete p.inj[i];
});
return p;
},
function() { // init
return {inj: {}};
}
);
Here we assume that there might be rows of data with the same uid and different inj arrays. This is more general than needed for your sample data: you could probably do something simpler if there is indeed only one row of data for each uid.
To flatten out the resulting group, with we can use a "fake group" to create one group-like {key, value} data item for each [uid, inj] pair:
function flatten_group(group, field) {
return {
all: function() {
var ret = [];
group.all().forEach(function(kv) {
Object.keys(kv.value[field]).forEach(function(i) {
ret.push({
key: [kv.key, +i],
value: kv.value[field][i]
});
})
});
return ret;
}
}
}
var uidinjGroup = flatten_group(uidGroup, 'inj');
Fork of your fiddle
In the fiddle, I've added a bar chart to demonstrate filtering by UID. Filtering on the bar chart works, but filtering on the scatter plot does not. If you need to filter on the scatter plot, that could probably be fixed, but it could only filter on the uid dimension because your data is too course to allow filtering by inj.

dc.js: How to change the data distribution in pie chart using different input threshold?

I would like to use the score from input field to change the data distribution in pie chart.
For example, the distribution "71% High/ 28% Low" with scoreThreshold 0.5 will be changed to "42% High/ 57% Low" with scoreThreshold 0.7.
I made this example here, but the result was not satisfactory: when typing 0.5 in the input field and clicking "check!", the pie chart distribution does not change.
This decides the distribution:
//## pie chart
var coreCount = ndx.dimension(function (d) {
var maxNumber=80;
if (typeof scoreThreshold!='number') {scoreThreshold=0.5}
//console.log(scoreThreshold)
if (d.scores >maxNumber*scoreThreshold)
{return 'High';}
else {return 'Low';}
});
I would like to renew the coreCount function to reassign the distribution using input score threshold. But it does not work:
$('#scoreThresholdBt').click(function () {
scoreThreshold=document.getElementById('scoreThreshold').value
scoreThreshold=parseFloat(scoreThreshold);
console.log(scoreThreshold,typeof scoreThreshold);
function redo_coreCount () {
var coreCount = ndx.dimension(function (d) {
var maxNumber=80;
console.log(scoreThreshold);
if (d.count >maxNumber*scoreThreshold)
{return 'High';}
else {return 'Low';}
});
}; redo_coreCount();
coreCount.group();/**/
dc.renderAll();
dc.redrawAll();
});
How can I realize this function?
I really appreciate any help.
You are essentially putting the new dimension in a temporary variable which gets thrown away immediately. coreCount in the click function is unrelated to the variable with the same name at the global level.
Likewise, coreCount.group() is not an action; it's constructing a group object which will also get lost if you don't use it.
You need to assign the new dimension and group to your chart, since it doesn't track the value of the global or the local coreCount.
So let's change the function to return a new dimension based on reading the score threshold from the input:
function coreCount_from_threshold() {
var scoreThreshold=document.getElementById('scoreThreshold').value;
scoreThreshold=parseFloat(scoreThreshold);
console.log(scoreThreshold,typeof scoreThreshold);
if (isNaN(scoreThreshold)) {scoreThreshold=0.5}
return ndx.dimension(function (d) {
var maxNumber=80;
if (d.scores >maxNumber*scoreThreshold)
{return 'High';}
else {return 'Low';}
});
}
Note that we need to use isNaN here, because typeof NaN === 'number'
We can use this both at initialization time:
var coreCount = coreCount_from_threshold();
and on click:
$('#scoreThresholdBt').click(function () {
coreCount.dispose();
coreCount = coreCount_from_threshold();
coreCountGroup = coreCount.group();
coreYesNoPieChart
.dimension(coreCount)
.group(coreCountGroup);
dc.redrawAll();
});
Note that we are assigning to the same global variables coreCount and coreCountGroup (because we don't use var here). We first dispose the old dimension, because otherwise it would continue to filter and take up resources. Then we assign the new dimension and group to the chart (because otherwise it won't know about them).
We only need to do a redraw (not a render) here, because dc.js charts can just update when they get new data.
Here is a working fork of your fiddle.

NVD3.js Given X-Axis Value, Get all Lines' Y-Axis Values

I'm using the line-with-focus chart ( View Finder ) example in nvd3. That means there's 3 or 4 lines ( series ) being drawn on the graph. When i hover over any of the lines I want to get back all the y-values for all lines of that given x-axis position ( for the most part these will be interpolated y-values per line ).
I see in the nv.models.lineWithFocusChart source code that using a callback for the elementMouseover.tooltip event I can get my data's x-value back for the data points on the line.
The closest part of the source code that does what i want is with the interactiveGuideline code for the lineChart examples. However, i don't want to create a <rect> overlay with elementMousemove interaction. I think i can modify this code to filter my data and get each line's y-value, but I'm sure there's an easier way I'm not seeing.
I think I'm on the right track, but just wondering if someone had this need before and found a quicker route than the rabbit hole I'm about jump in.
Thanks for feedback
This is the basic functionality you're looking for, it still needs a bit of finesse and styling of the tooltips. (Right now the tooltip blocks the view of the points...)
Key code to call after the drawing the chart in (for example, within the nv.addGraph function on the NVD3 live code site):
d3.selectAll("g.nv-focus g.nv-point-paths")
.on("mouseover.mine", function(dataset){
//console.log("Data: ", dataset);
var singlePoint, pointIndex, pointXLocation, allData = [];
var lines = chart.lines;
var xScale = chart.xAxis.scale();
var yScale = chart.yAxis.scale();
var mouseCoords = d3.mouse(this);
var pointXValue = xScale.invert(mouseCoords[0]);
dataset
.filter(function(series, i) {
series.seriesIndex = i;
return !series.disabled;
})
.forEach(function(series,i) {
pointIndex = nv.interactiveBisect(series.values, pointXValue, lines.x());
lines.highlightPoint(i, pointIndex, true);
var point = series.values[pointIndex];
if (typeof point === 'undefined') return;
if (typeof singlePoint === 'undefined') singlePoint = point;
if (typeof pointXLocation === 'undefined')
pointXLocation = xScale(lines.x()(point,pointIndex));
allData.push({
key: series.key,
value: lines.y()(point, pointIndex),
color: lines.color()(series,series.seriesIndex)
});
});
/*
Returns the index in the array "values" that is closest to searchVal.
Only returns an index if searchVal is within some "threshold".
Otherwise, returns null.
*/
nv.nearestValueIndex = function (values, searchVal, threshold) {
"use strict";
var yDistMax = Infinity, indexToHighlight = null;
values.forEach(function(d,i) {
var delta = Math.abs(searchVal - d);
if ( delta <= yDistMax && delta < threshold) {
yDistMax = delta;
indexToHighlight = i;
}
});
return indexToHighlight;
};
//Determine which line the mouse is closest to.
if (allData.length > 2) {
var yValue = yScale.invert( mouseCoords[1] );
var domainExtent = Math.abs(yScale.domain()[0] - yScale.domain()[1]);
var threshold = 0.03 * domainExtent;
var indexToHighlight = nv.nearestValueIndex(
allData.map(function(d){ return d.value}), yValue, threshold
);
if (indexToHighlight !== null)
allData[indexToHighlight].highlight = true;
//set a flag you can use when styling the tooltip
}
//console.log("Points for all series", allData);
var xValue = chart.xAxis.tickFormat()( lines.x()(singlePoint,pointIndex) );
d3.select("div.nvtooltip:last-of-type")
.html(
"Point: " + xValue + "<br/>" +
allData.map(function(point){
return "<span style='color:" + point.color +
(point.highlight? ";font-weight:bold" : "") + "'>" +
point.key + ": " +
chart.yAxis.tickFormat()(point.value) +
"</span>";
}).join("<br/><hr/>")
);
}).on("mouseout.mine", function(d,i){
//select all the visible circles and remove the hover class
d3.selectAll("g.nv-focus circle.hover").classed("hover", false);
});
The first thing to figure out was which objects should I bind the events to? The logical choice was the Voronoi path elements, but even when I namespaced the event names to avoid conflict the internal event handlers nothing was triggering my event handling function. It seems that a parent <g> event captures the mouse events before they can reach the individual <path> elements. However, it works just fine if instead I bind the events to the <g> element that contains the Voronoi paths, and it has the added benefit of giving me direct access to the entire dataset as the data object passed to my function. That means that even if the data is later updated, the function is still using the active data.
The rest of the code is based on the Interactive Guideline code for the NVD3 line graphs, but I had to make a couple important changes:
Their code is inside the closure of the chart function and can access private variables, I can't. Also the context+focus graph has slightly different names/functionality for accessing chart components, because it is made up of two charts. Because of that:
chart in the internal code is chart.lines externally,
xScale and yScale have to be accessed from the chart axes,
the color scale and the x and y accessor functions are accessible within lines,
I have to select the tooltip instead of having it in a variable
Their function is called with custom event as the e parameter that has already had the mouse coordinates calculated, I have to calculate them myself.
One of their calculations uses a function (nv.nearestValueIndex) which is only initialized if you create an interactive layer, so I had to copy that function definition into mine.
I think that about covers it. If there's anything else you can't follow, leave a comment.

Data joins in d3; I must not be understanding selections and/or data key functions properly

I'm working on a simple d3 example where I use d3 to place some new divs on a page, add attributes, and add data-driven styles. The part that is tripping me up is when I want to use d3 to update some styles using new data. I've pasted the code from a jsFiddle ( http://jsfiddle.net/MzPUg/15/ ) below.
In the step that originally creates the divs, I use a key function to add indexes to the elements and in the update step (the part that isn't working) I also use a key function. But what isn't clear from the d3 documentation is how the actual data join works (e.g. where are indexes stored in the DOM elements? what if there are duplicate indexes?, etc.).
So, there are obvious gaps in my knowledge, but keeping it simple here can anyone shed light on why this example is not working? Any additional info on the precise nature of data joins in d3 would be frosting on the cake. (I've already seen http://bost.ocks.org/mike/join/.)
//add a container div to the body and add a class
var thediv = d3.select("body").append("div").attr("class","bluediv");
//add six medium-sized divs to the container div
//note that a key index function is provided to the data method here
//where do the resulting index value get stored?
var mediumdivs = thediv.selectAll("div")
.data([10,50,90,130,170,210],function(d){return d})
.enter().append("div")
.style("top",function(d){return d + "px"})
.style("left",function(d){return d + "px"})
.attr("class","meddiv")
//UPDATE STEP - NOT WORKING
//Attempt to update the position of two divs
var newdata = [{newval:30,oldval:10},{newval:80,oldval:50}]
var mediumUpdate = mediumdivs.data(newdata,function(d){return d.oldval})
.style("left",function(d){return d.newval + "px"})
As far as I know, you do not update the elements that already exist. Instead, you tell D3 which elements to draw and it determines what to remove or update on the screen.
I updated your JSFiddle with working code. I have also added the code below.
//add a container div to the body and add a class
var thediv = d3.select("body").append("div").attr("class", "bluediv");
function update(data) {
var mediumdivs = thediv.selectAll("div").data(data, function(d) {
return d;
});
// Tell D3 to add a div for each data point.
mediumdivs.enter().append("div").style("top", function(d) {
return d + "px";
}).style("left", function(d) {
return d + "px";
}).attr("class", "meddiv")
// Add an id element to allow you to find this div outside of D3.
.attr("id", function(d) {
return d;
});
// Tell D3 to remove all divs that no longer point to existing data.
mediumdivs.exit().remove();
}
// Draw the scene for the initial data array at the top.
update([10, 50, 90, 130, 170, 210]);
// Draw the scene with the updated array.
update([30, 80, 90, 130, 170, 210]);
I am not sure of D3's inner workings of how it stores indexes, but you can add an id attribute to the divs you create to create unique indexes for yourself.
In the above answer an update step is needed for transition of divs with the same key. illustrative jsfiddle showing what happens with/without update function.
Update function is just selection.stuff, rather than selection.enter().stuff :
//add a container div to the body and add a class
var updateDiv = d3.select("#updateDiv").attr("class", "bluediv");
var noUpdateDiv = d3.select("#noUpdateDiv").attr("class", "bluediv");
function update(selection,data,zeroTop,withUpdate) {
//add six medium-sized divs to the container div
//note that a key index function is provided to the data method here
//where do the resulting index value get stored?
var mediumdivs = selection.selectAll("div").data(data, function(d) {
return d;
});
if(withUpdate){
mediumdivs.style("top", function(d) {
if(zeroTop){
return 0
}else{
return d + "px";
}
}).style("left", function(d) {
return d + "px";
}).attr("class", "meddiv");
}
mediumdivs.enter().append("div").style("top", function(d) {
if(zeroTop){
return 0
}else{
return d + "px";
}
}).style("left", function(d) {
return d + "px";
}).attr("class", "meddiv");
mediumdivs.exit().remove();
}
//with the update function we maintain 3 of the old divs, and move them to the top
update(updateDiv,[10, 50, 90, 130, 170, 210],false,true);
update(updateDiv,[10,50,90],true,true);
//without the update function divs are maintained, but not transitioned
update(noUpdateDiv,[10, 50, 90, 130, 170, 210],false,false);
update(noUpdateDiv,[10,50,90],true,false);
The other answers given so far use the strategy of removing and recreating divs. This isn't necessary. The problem with Al R.'s original code was just in the way it used the data key. The same data key function is used both for the old data and for the data that's newly passed in. Since in Al R.'s example, the old data was a simple array of numbers, and the new data was an array of objects with properties, no data was selected in the mediumUpdate line.
Here's one way to make the selection work:
var newdata = [10, 50];
var newdatamap = {10:30, 50:80};
var mediumUpdate = mediumdivs.data(newdata, function(d){return d;})
.style("left",function(d){return newdatamap[d] + "px";});
Here's a jsfiddle, which also changes the color of the selected divs to make the effect obvious.

Categories

Resources