I want to move from well established css styles section and use of classes :
<style>
land { fill:grey; }
focus { fill:yellow; }
</style>
svg.
... // some stuff here
.attr("class","focus");
...to pure js variables :
var land = { 'fill':'grey' };
var focus= { 'fill':'yellow'};
I observe the following...
Conclusion
Passing 2 arguments, with the 2nd a function (1) and or one argument as a variable (2) work perfectly :
.attr("class", function(d){ if(){...}else{...} }) // (1) works
.attr(land) // (2) works
but this exact same function in (1) FAILS when I pass it as a single argument function (3) :
.attr(function(){ return land; }) // fails. Expected to return variable `land`, as for (2).
Any idea if this is an intended d3js behavior ? What do I do wrong ? And, possibly, how to make it work.
Migrate from css styles to pure js as follow.
Your former css:
<style>
land { fill:grey; }
focus { fill:yellow; }
</style>
...resulting js variables :
var land = "fill:grey" ; // no {}
var focus= "fill:yellow, stroke-color: #F00"; // , or ; should be ok.
...and d3js :
...
.attr("style", function(){return land}) // this is the way, together with no {} !
Related
There is a way to change a background or text color to a row in PrimeReact Datatable that is using rowClassName={rowClass} where rowClass is a function that allows returning a class configured in the CSS file.
but... what if I want to choose an arbitrary color? for example, one fetched from a database in #RRGGBB format?
Reading de documentation I can't see a way to call a function to return the style string. Another way could be, creating dynamically the class name, for example...
class RRGGBB for a selected color, define this class with background: #RRGGBB and let rowClassName={rowClass} call rowClass function returning this dynamically created class...
I have this approach, but don't work:
const mycolor = "#00ff00";
function createStyle() {
const style = document.createElement("style");
// add CSS styles
style.innerHTML = `
.lulu {
color: white;
background-color: ${mycolor};
}
`;
// append the style to the DOM in <head> section
document.head.appendChild(style);
}
createStyle();
const rowClass = (data) => {
return {
"lulu": data.category === "Accessories"
};
};
.....
<DataTable value={products} rowClassName={rowClass}>
this code is a modified version of the sample code by prime react, here, in sandbox:
https://codesandbox.io/s/o6k1n
thanks!
I have solved it...
What I did is to create a dynamic css, and then use it:
function createStyle(color) {
var style = document.getElementsByTagName("style");
var colortag = color.replace("#", "mycolor");
//Assuming there is a style section in the head
var pos = style[0].innerHTML.indexOf(colortag);
if(pos<0)
style[1].innerHTML += "."+colortag+`
{
color: ${color}!important;
}
`;
return colortag;
const rowClass = (data) => {
var colortag;
if (data.COLOR!=undefined)
colortag=createStyle(data.COLOR);
return { [colortag]: ata.COLOR!=undefined };
}
<DataTable ref={dt} value={Data} rowClassName={rowClass}>
<column>......</column>
</DataTable>
With this code, if in the data there is a field called COLOR:"#RRGGBB" then it will create a style with this color and use it as text color. The same can be applied to background or whatever
I've worked out a consistent pattern for using the new selection merge which is brilliant for reusable charts where data and/or scales may change.
I've also been using the key function successfully.
However, I seem to get a problem when entering and appending a group with multiple elements. The data is successfully updated in the group but not the appended elements.
I've got round it by adding a fix (see below) but I'm sure it is something really obvious that needs to be changed to resolve it.
Any thoughts?
//define data group
var my_group = svg.selectAll(".data_group")
.data(my_data,function(d){return d.id});
//enter new groups
var enter = my_group.enter()
.append("g")
.attr("class","data_group");
//append items to group
enter.append("text").attr("class","group_item group_text")
enter.append("circle").attr("class","group_item group_circle");
//merge and remove
my_group.merge(enter);
my_group.exit().remove();
//fix added to reset changing data for bars.
d3.selectAll(".group_item").each(function(d){
d3.select(this)._groups[0][0].__data__ = d3.select(this)._groups[0][0].parentElement.__data__;
});
d3.selectAll(".group_text")
.... add properties to text - ie x,y,fill,text-anchor,text
d3.selectAll(".group_circle")
.... add properties to circle - ie cx,cy,fill,stroke,radius
There is absolutely no need for selecting the parent group, getting its data and rebinding it to the child elements, as the code in your question and the other answer do. This is bending over backwards. Also, do not delete/re-append elements, as suggested, which is not an idiomatic D3 approach.
The thing is simple: the new data is there for the children elements in the "enter" selection. You just need to use the parent's selection (with select()) to propagate them.
Here is a basic demo, using (most of) your code. The code generates from 1 to 5 data objects, with a random property called someProperty. You'll see that, using your each(), only the children elements in the "enter" selection are changed:
var svg = d3.select("svg");
d3.interval(function() {
var data = d3.range(1 + ~~(Math.random() * 4)).map(function(d) {
return {
id: "id" + d,
"someProperty": ~~(Math.random() * 100)
}
});
update(data);
}, 2000);
function update(my_data) {
var my_group = svg.selectAll(".data_group")
.data(my_data, function(d) {
return d.id
});
my_group.exit().remove();
var enter = my_group.enter()
.append("g")
.attr("class", "data_group");
enter.append("text").attr("class", "group_item group_text")
enter.append("circle").attr("class", "group_item group_circle");
my_group = my_group.merge(enter);
console.log("---")
d3.selectAll(".group_text").each(function(d) {
console.log(JSON.stringify(d))
});
}
.as-console-wrapper { max-height: 100% !important;}
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg></svg>
Now, if we use your parent's selection...
my_group.select(".group_text").each(function(d) {
console.log(d)
})
... you'll see that all properties are updated:
var svg = d3.select("svg");
d3.interval(function() {
var data = d3.range(1 + ~~(Math.random() * 4)).map(function(d) {
return {
id: "id" + d,
"someProperty": ~~(Math.random() * 100)
}
});
update(data);
}, 2000);
function update(my_data) {
var my_group = svg.selectAll(".data_group")
.data(my_data, function(d) {
return d.id
});
my_group.exit().remove();
var enter = my_group.enter()
.append("g")
.attr("class", "data_group");
enter.append("text").attr("class", "group_item group_text")
enter.append("circle").attr("class", "group_item group_circle");
my_group = my_group.merge(enter);
console.log("---")
my_group.select(".group_text").each(function(d) {
console.log(d)
})
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg></svg>
Finally, in your now deleted answer you're using my_group.selectAll(). The problem is that selectAll() does not propagate the data.
Have a look at this table I made:
Method
select()
selectAll()
Selection
selects the first element that matches the selector string
selects all elements that match the selector string
Grouping
Does not affect grouping
Affects grouping
Data propagation
Propagates data
Doesn't propagate data
Pay attention to the propagates data versus doesn't propagate data.
The more d3 way of copying the data bound to the parent g elements
No need to add the fix
d3.selectAll(".group_text")
.datum(function () { return d3.select(this.parentNode).datum(); } )
// .... add properties to text - ie x,y,fill,text-anchor,text
d3.selectAll(".group_circle")
.datum(function () { return d3.select(this.parentNode).datum(); } )
// .... add properties to circle - ie cx,cy,fill,stroke,radius
I have g.row elements containing g.cell elements, each containing a rect element. My nested data is bound to g.row and then g.cell. The rect elements access the data bound to g.cell.
At the moment my enter and exit selections add and remove g.cell. It would be more efficient to have them add and remove the rect elements, because g.cell has events bound to it that I need to reassign. But is this possible? I can't see how to get it to work.
I've managed to run cell.exit().selectAll("rect").remove(); which works fine. But cell.enter().selectAll("g.cell").append("rect"); throws an error ("[this code] is not a function"). While cell.enter().append("rect") doesn't append a rect.
Current code on g.cell:
var cell = row.selectAll("g.cell")
.data(function(d){
return d.value.filter(function(p){
if (p[1]=='') {
return horizNodesCopy.indexOf(p[0])!=-1;
} else {
return horizNodesCopy.indexOf(p[0]+' -- '+p[1])!=-1;
}
});
});
var cell2 = cell.enter().append("g")
.attr("class",function(d,i,j){ return "cell cell_"+i; })
.attr('transform',function(d,i,j){
if (d[1]=='') {
return 'translate('+ x(d[0]) +',0)';
} else {
return 'translate('+ x(d[0]+' -- '+d[1]) +',0)';
}
});
addRectangles(cell2,colorScale);
cell.exit().remove();
This feels like it's going to be something obvious :/
Brush extent needs to be changed only from a dropdown as shown here: https://jsfiddle.net/dani2011/67jopfj8/3/
Need to disable brush extending by:
1) Extending an existing brush using the handles/resize-area of the brush
Gray circles are the handels:
2) Dragging a new brush by clicking on the brush background, where the
haircross cursor appears.
JavaScript file
Removed the handles of the brush:
timeSlider.on('preRedraw',function (chart)
{
var timesliderSVG = d3.select("#bitrate-timeSlider-chart").selectAll("g.brush").selectAll("g.resize").selectAll("*").data(data[0]).exit().remove();})
If using css instead:
#bitrate-timeSlider-chart g.resize {
display:none;
visibility:hidden;
Now it looks like this:
.
The rect and the path elements inside "resize e","resize w" were removed:
However,the "resize e", "resize w" for extanding the brush still exist:
g.resize.e and g.resize.w dimesions are 0*0:
Furthurmore,after deleting "resize e","resize w" in the "developer tools/elements" in chrome,they reappear after moving the brush.
Tried to remove the resize-area in brushstart,brush,brushend:
timeSlider.on('renderlet', function (chart) {
var brushg = d3.select("#bitrate-timeSlider-chart").selectAll("g.brush");
var resizeg = brushg.selectAll("g.resize").selectAll("*").data(data[0]);
var timesliderSVG4 =
brushg.on("brushstart", function () {resizeg.exit().remove()}).on("brush", function () { resizeg.exit().remove() }).on("brushend", function () {resizeg.exit().remove() })
dc.js file
Tried to change setHandlePaths,resizeHandlePath:
1)
Remarked the _chart.setHandlePaths(gBrush):
_chart.renderBrush = function (g) {....
// _chart.setHandlePaths(gBrush);
...}
2) Changed _chart.setHandlePaths = function (gBrush) for example by removing the gbrush.path:
// gBrush.selectAll('.resize path').exit().remove();
3) Remarked/changed _chart.resizeHandlePath = function (d) {...}.
d3.js file
1) Remarked/changed resize such as:
mode: "move" //mode: "resize" ,
var resize = g.selectAll(".resize").data(resizes[0], d3_identity);
Using resizes[0] disable the brush rendering on the background but still can re-extend the existing brush
2) Remarked/changed d3_svg_brushResizes
3) In d3.svg.brush = function():
a) Added .style("display","none"):
background.enter().append("rect").attr("class", "background").style("visibility", "hidden").style("display", "none").style("cursor", "none");
b) background.exit().remove();
The cursor now is "pointer" instead of "haircross" extending the brush to a full width
c) d3_svg_brushCursor disabled makes the whole brush disappear
4) Changed the pointer-events as specified here: https://developer.mozilla.org/en/docs/Web/CSS/pointer-events
5) console.log in different places to track the different brush events:
function d3_event_dragSuppress(node) {
console.log("here2 ");
}
if (d3_event_dragSelect) {
console.log("here3 d3_event_dragSelect");
...
}
return function (suppressClick) {
console.log("suppressClick1");
...
var off = function () {
console.log("suppressClick2");
...
w.on(click, function () {
console.log("suppressClick3")
...
function d3_mousePoint(container, e) {
console.log("d3_mousePoint1")
...
if (svg.createSVGPoint) {
console.log("createSVGPoint");
...
if (window.scrollX || window.scrollY) {
console.log("createSVGPoint1");
svg = d3.select("body").append("svg").style({
...
function dragstart(id, position, subject, move, end) {
console.log("dragstart")
...
function moved() {
console.log("moved ");
console.log("transition1");
...
if (d3.event.changedTouches) {
console.log("brushstart1");
...
} else {
console.log("brushstart2");
..
if (dragging) {
console.log("dragging4");
....
if (d3.event.keyCode == 32) {
if (!dragging) {
console.log("notdragging1");
...
function brushmove() {
console.log("brushmove");
...
if (!dragging) {
console.log("brushmove!dragging");
if (d3.event.altKey) {
console.log("brushmove!dragging1");
...
if (resizingX && move1(point, x, 0)) {
console.log("resizeXMove1");
...
if (resizingY && move1(point, y, 1)) {
console.log("resizeYMove1");
...
if (moved) {
console.log("moved");
...
}
function move1(point, scale, i) {
if (dragging) {
console.log("dragging1");
...
if (dragging) {
console.log("dragging2");
...
} else {
console.log("dragging10");
...
if (extent[0] != min || extent[1] != max) {
console.log("dragging11");
if (i) console.log("dragging12"); yExtentDomain = null;
console.log("dragging13");
function brushend() {
console.log("brushend");
...
The two changes that seemed to get closest to the needed result are in d3.js:
1) Using resizes[0] disables the brush rendering on the background but still can re-extend the existing brush
var resize = g.selectAll(".resize").data(resizes[0], d3_identity);
2) Removing the brush's background changes the cursor to "pointer" instead of "haircross",extending the brush to a full width only when clicking on the graph
`background.exit().remove();`
Any help would be very appreciated!
This is from the accepted answer in Disable d3 brush resize, as suggested by #altocumulus. I didn't see a response from #Dani on this idea in particular, but thought I'd go ahead and try it, since I've seen other people try it in the past. (Probably on the dc.js users group.)
It looks a little twitchy, because d3.js will draw the brush at the new extent, and then a moment later we reset the extent to what we want, but functionally it seems to do what we want.
In dc.js the function that handles brush "rounding" is coordinateGridMixin.extendBrush:
_chart.extendBrush = function () {
var extent = _brush.extent();
if (_chart.round()) {
extent[0] = extent.map(_chart.round())[0];
extent[1] = extent.map(_chart.round())[1];
_g.select('.brush')
.call(_brush.extent(extent));
}
return extent;
};
Notice that it's following the same pattern as Lars' answer. Well, this is sort of like rounding, right? Let's override it.
First, let's store the current number of hours when it's set through the dropdown:
var graphSpan;
function addHours(amountHours) {
graphSpan = amountHours;
// ...
Next let's override coordinateGridMixin.extendBrush:
timeSlider.extendBrush = function() {
var extent = timeSlider.brush().extent();
if(graphSpan) {
extent[1] = moment(extent[0]).add(graphSpan, 'hours');
}
return extent;
}
We just replace the function. If we needed to reuse the old implementation in our function, we could use dc.override.
If graphSpan has been set, we add that amount to the beginning to get the end. If it's not set, we allow the user to specify the brush width - you'd need to default the graphSpan and the select widget if you wanted that part to work automatically.
Well, let's admit it: it's very twitchy, but it works:
EDIT: Got rid of the twitch! The problem was that dc.js was setting the brush extent asynchronously after a little while (in response to the filter event). If we also set it during extentBrush then it never shows the wrong extent:
timeSlider.extendBrush = function() {
var extent = timeSlider.brush().extent();
if(graphSpan) {
extent[1] = moment(extent[0]).add(graphSpan, 'hours');
timeSlider.brush().extent(extent);
}
return extent;
}
https://jsfiddle.net/gordonwoodhull/xdo05chk/1/
What worked for me:
in d3:
disable resize handles
d3.selectAll('.brush>.handle').remove();
disable crosshair
d3.selectAll('.brush>.overlay').remove();
or
in css:
disable resize handles -
.handle {
pointer-events: none;
}
disable crosshair -
.overlay {
pointer-events: none;
}
I am trying to use Mike Bostock's zoomable treemap http://bost.ocks.org/mike/treemap/ with one modification. Instead of using nested JSON data, I have have a simple mapping from parents to a list of children. I built a function, getChildren(root), that simply returns root's children, or null if root does not have any children.
I have tried replacing all instances of d.children() with getChildren(d) in the treemap javascript file, but it seems that it is not working properly.
The resulting page shows the orange bar as normal up top, but nothing else displays correctly (i.e. there are no rectangles underneath the orange bar, just empty gray space). All the text from the children is mashed up in the top left corner of the empty gray space, so it might be that coordinates are not being assigned correctly.
Any ideas??
Thanks!
It looks like there were a few issues here:
Your data structure doesn't seem to be referencing the child nodes:
var nMap = {};
nMap.papa = {};
nMap.papa["children"] = [];
nMap.papa["children"].push({
"name": "c1"
});
// snip
nMap.c1 = {
size: 5
};
Unless I'm missing something, your getChildren function gets the { name: "c1" } object but never looks up nMap.c1. I'm not exactly certain what your alternative data structure is trying to achieve, but it seems like the most obvious option is to use a flat map of nodes, with children referenced by id, like this:
var nMap = {};
nMap.c1 = {
name: "c1",
value: 5
};
nMap.c2 = {
name: "c2",
value: 5
};
nMap.c3 = {
name: "c3",
value: 5
};
nMap.papa = {
name: "papa",
children: ['c1', 'c2', 'c3']
};
With a structure like this, you can map to the real children in the getChildren function:
function getChildren(par){
var parName = par.name,
childNames = parName in nMap && nMap[parName].children;
if (childNames) {
// look up real nodes
return childNames.map(function(name) { return nMap[name]; });
}
}
Your children were using size instead of value to indicate weight, and the rest of the code expected value (so they all had weight 0).
Because you're using the "zoomable" treemap approach, which uses a specialized version of the treemap layout, you don't need to specify the .children accessor of the treemap layout. Instead, use your custom accessor in the the custom layout helper:
function layout(d) {
// get the children with your accessor
var children = getChildren(d);
if (children && children.length > 0) {
treemap.nodes({ children: children });
children.forEach(function(c) {
c.x = d.x + c.x * d.dx;
c.y = d.y + c.y * d.dy;
c.dx *= d.dx;
c.dy *= d.dy;
c.parent = d;
layout(c);
});
}
}
Working fiddle here: http://jsfiddle.net/nrabinowitz/WpQCy/