How to turn a raw DOM element in to a D3 selection? - d3.js

Scenario: I have several svg elements on a page, each displaying a different graph. When a graph receives a mouse click it triggers an event handler wherein this corresponds to the raw svg element that was clicked. When this happens, I want to select the graph's path element with D3 in order modify it.
I know that I could give each graph an ID and then use that to make a D3 selection, e.g.
function on_click( event ) {
var path = d3.select( '#' + this.id ).select( 'path' );
path.do_stuff...
}
but I wondered whether there was an equivalent of jQuery's feature of turning raw DOM elements in to a jQuery object, e.g.
jQuery( my_raw_dom_element ).do_stuff...

Yes, you can simply do
d3.select(my_raw_dom_element);

Related

amCharts 4 - How to Access Currently-Hovered Series Data/Color in XYChart in JavaScript

I'm trying to access the currently-hovered series data and color via JavaScript. The data is available to the legend and tooltip, but I'm not sure how to directly access it.
It's possible to place the legend in an external container, but their code creates a lot of additional containers/wrappers which makes formatting difficult. This Github question addresses it, but no answer was provided.
Perhaps events could be used to detect changes in the legend text or tspan elements and then grab the new text, but I'm not sure how to do this (using amCharts events) and how efficient it would be (especially with multiple series and/or charts with synced cursors).
Another idea was to get the data based on cursor position, but this seems inefficient (cursorpositionchanged fires too often - on mouse/cursor movement even when the series data hasn't changed). Maybe it could be done more efficiently based on change in dateAxis value? For example, using the positionchanged event listener:
chart.cursor.lineX.events.on('positionchanged', function() {
// get series data and do something with it
});
At least when using chart.cursor.xAxis = dateAxis, the positionchanged event only seems to fire when the cursor jumps to a new value. So it would be more efficient than an event that fired on mouse/cursor movement.
Any suggestions would be appreciated.
UPDATE
By currently-hovered, I am referring to the series data and color accessible via the tooltip (for example) with the mouse over the chart.
Examples: CandlestickSeries and LineSeries
One method you can try is to set an adapter for tooltipText on the object of concern. Since this may run multiple times especially via a chart cursor, perhaps keep track of changes to the tooltip via monitoring the unique value, e.g. in the samples provided that would be the date field. The data you're looking for can be found in the adapter's target.tooltipDataItem. The color, if on the series, will be target.tooltipDataItem.component.fill (in the case of the line series example, the target is the line series and has no change of color, so you can just use target.fill), otherwise e.g. in the case of CandleStick series the color would be on the candle stick, or column, i.e. via target.tooltipDataItem.column.fill.
Sample adapter for LineSeries:
var tooltipDate;
series.adapter.add("tooltipText", function(text, target) {
// data via target.tooltipDataItem.dataContext
console.log('text adapter; color: ', target.tooltipDataItem.component.fill.hex);
if (tooltipDate !== target.tooltipDataItem.dataContext.date) {
console.log('new tooltip date, do something');
tooltipDate = target.tooltipDataItem.dataContext.date;
}
// note: in this case: component === target
return text;
});
Demo:
https://codepen.io/team/amcharts/pen/9f621f6a0e5d0441fe55b99a25094e2b
Sample Candlestick series adapter:
var tooltipDate;
series.adapter.add("tooltipText", function(text, target) {
// data via target.tooltipDataItem.dataContext
console.log('text adapter; color: ', target.tooltipDataItem.column.fill.hex);
if (tooltipDate !== target.tooltipDataItem.dataContext.date) {
console.log('new tooltip date, do something');
tooltipDate = target.tooltipDataItem.dataContext.date;
}
return text;
});
Demo:
https://codepen.io/team/amcharts/pen/80343b59241b72cf8246c266d70281a7
Let us know if this is making sense, and if the adapter route is a good point in time to capture changes, data, color, as well as if it's efficient enough a manner to go about this.

Adjusting small multiples sparklines

I have an heatmap that show some data and a sparkline for each line of the heatmap.
If the user click on a row label, then the data are ordered in decreasing order, so each rect is placed in the right position.
Viceversa, if the user click on a column label.
Each react is placed in the right way but I'm not able to place the sparkline.
Here the code.
When the user click on a row label, also the path inside the svg containing the sparkline should be updated.
And then, when the user click on a column label, the svg containing the sparkline should be placed in the correct line.
To place the svg in the right place, I try to use the x and y attributes of svg. They are updated but the svg doesn't change its position. Why?
Here is a piece of code related to that:
var t = svg.transition().duration(1000);
var values = [];
var sorted;
sorted = d3.range(numRegions).sort(function(a, b) {
if(sortOrder) {
return values[b] - values[a];
}
else {
return values[a] - values[b];
}
});
t.selectAll('.rowLabel')
.attr('y', function(d, k) {
return sorted.indexOf(k) * cellSize;
});
Also, I don't know how to change the path of every sparkline svg. I could take the data and order them manually, but this is only good for the row on which the user has clicked and not for all the others.
How can I do?
The vertical and horizontal re-positioning/redrawing of those sparklines require different approaches:
Vertical adjustment
For this solution I'm using selection.sort, which:
Returns a new selection that contains a copy of each group in this selection sorted according to the compare function. After sorting, re-inserts elements to match the resulting order.
So, first, we set our selection:
var sortedSVG = d3.selectAll(".data-svg")
Then, since selection.sort deals with data, we bind the datum, which is the index of the SVG regarding your sorted array:
.datum(function(d){
return sorted.indexOf(+this.dataset.r)
})
Finally, we compare them in ascending order:
.sort(function(a,b){
return d3.ascending(a,b)
});
Have in mind that the change is immediate, not a slow and nice transition. This is because the elements are re-positioned in the DOM, and the new structure is painted immediately. For having a slow transition, you'll have to deal with HTML and CSS inside the container div (which may be worth a new specific question).
Horizontal adjustment
The issue here is getting all the relevant data from the selection:
var sel = d3.selectAll('rect[data-r=\'' + k + '\']')
.each(function() {
arr.push({value:+d3.select(this).attr('data-value'),
pos: +d3.select(this).attr('data-c')});
});
And sorting it according to data-c. After that, we map the result to a simple array:
var result = arr.sort(function(a,b){
return sorted.indexOf(a.pos) - sorted.indexOf(b.pos)
}).map(function(d){
return d.value
});
Conclusion
Here is the updated Plunker: http://next.plnkr.co/edit/85fIXWxmX0l42cHx or http://plnkr.co/edit/85fIXWxmX0l42cHx
PS: You'll need to re-position the circles as well.

Insert HTML before an element in CKEditor

What is the best way to programmatically insert HTML (that represents a CKEditor widget) before an existing element in CKEditor?
The content editable is not in focus and is not currently being edited.
For example, suppose the contents of the editor are:
<h1>Here's a title</h1>
<h2>Here's a subtitle</h2>
<p>Here's a paragraph</p>
<p>Here's a paragraph</p>
<p>Here's a paragraph</p>
Now, say I have a reference to the second <p> element. What is the best way to insert html before this tag? (Keeping in mind that the HTML that I want to insert will become a Ckeditor widget after inserting.)
Thank you very much for any help,
Michael
With the current API it is not possible to insert HTML string at the specific position without involving selection (EDIT: since CKEditor 4.5.0 it is possible – read below), because the editor.insertHtml method inserts in the selection position. However, if you have a simple situation that your HTML string contains just one element (with some ancestors), then you can easily use editor.insertElement on a lower level, when you can specify range at which you want to insert element:
var range = editor.createRange(),
element = CKEDITOR.dom.element.createFromHtml( elementHtml );
// Place range before the <p> element.
range.setStartAt( elementP, CKEDITOR.POSITION_BEFORE_START );
// Make sure it's collapsed.
range.collapse( true );
// Insert element at the range position.
editor.editable().insertElement( element, range );
As you can see this code uses editable.insertElement, which is used by editor.insertElement.
PS. Remember that insertElement will not upcast and initialize your widget. You can find more about this here - CKEditor, initialize widget added with insertElement.
Since 4.5.0
CKEditor 4.5.0 introduced editor.editable().insertHtmlIntoRange() as well as a range parameter for editor.insertHtml(). The latter method is a more high-level one, so it will take care of undo manager and setting selection in place of insertion. The former one is more a low-level method and it only inserts the data.
If you want to insert an element between or outside of the paragraphs
, the CKEDITOR.POSITION_BEFORE_START flag won't work because the element will still be placed inside the <p></p> node.
However, the CKEDITOR.dom.node.insertBeforeMe() method will place the new element before any editor node without wrapping it or confining it to a text node.
var startRange = editor.getSelection(); //Cursor position
var parent = startRange.getStartElement(); //The parent <p> or <span> of the cursor
var e1 = CKEDITOR.dom.element.createFromHtml("<h3>Subtitle before paragraphs</h3>");
parent.insertBeforeMe(e1); //Places new node before the specified node
Hope this helps!
its smoothly simple as this
$(document).ready(function(){
$("#add1").click(function(){
CKEDITOR.instances.editor2.insertHtml( '<ul><li>Computers & Electronics</li></ul>' );
});
});

Calling a function once when data enters in d3

Is there a way to call a function when data enters using data().enter()?
For example, in this current jsfiddle: http://jsfiddle.net/p3m8A/4/ , I have a function that draws a group and I want to call this function when new data enters. The current jsfiddle doesn't do anything but the objective is to click on the red square and using .data.enter draw a purple square when the red square is clicked.
The specific part I'm trying to get to work is :
canvas.selectAll("#boxGroup")
.data(data)
.enter().function(d,i) {
drawBox(150,20,d);
};
Thanks
The callback passed to the call method is actually passed the selection, not the data.
enterSelection.call(function(selection){/* this === selection */});
So, what you were probably looking for is the each method.
enterSelection.each(function(d, i){/* this is selection */ drawBoxes(150,20,d);});
You want the method .call(function(d))
This will run your function once, passing d as the array of all of the data you have provided. i is not defined for using call after enter().
If you want to draw multiple boxes, based on d, your code would look something like this:
canvas.selectAll("boxGroup")
.data(data)
.enter()
.call(function(d){drawBoxes(150,20,d);});
I've created a basic fiddle of this here.
Note that this is what you want to use if you want to call a function on the selection returned by .enter() in the same spot as you're using it. It's also possible to bind a function to the enter event of a given DOM element by using .on('enter',function), but this would require that the element that you are entering data into already exist.

How data in a d3 scatterplot could be referenced/called outside d3 selection

I have a d3 scatterplot and want to know how to get the value of the data appended to one single record (circle) in order to call it later in the code.
Say we have the following data:
ID X Y
A 1 1
B 2 2
C 3 3
How can I get the X value of the record B?
Does anyone have an idea how to do it?
thx
You may need to show more code, as it depends on how you're attaching the data. But assuming that you have something like
var circle = svg.selectAll("circle").data(myArrayOfDataObjects)
.enter().append("circle");
You can assign an ID or class to the DOM element you create:
circle.attr("id", function(d) { return "dataRow" + d.ID; });
Now you can use d3, plain JS, or the library of your choice to get a handle on the DOM element later on. D3 attaches its data as a __data__ property on the DOM element, so you can reference that to get your datum. D3 also provides the .datum() method to get this value:
var myDataRow = d3.selectAll("#dataRowB").datum();
var xValue = myDataRow.X;
Or, in plain JavaScript:
var myDataRow = document.getElementById("#dataRowB").__data__;
// etc
If jQuery is an option, in your d3 you can use the jQuery .data() function to attach the d3 data directly to the SVG element. Simply run something like .each(function(d){ $(this).data(d) }).
You can then access the data anywhere by using $(selector).data(), where selector points to the DOM element or elements that you're interested in, with something like '#RecordB'.
If your data is more complex, (say, d = {foo: 'foo', bar: 'bar'}), you'd use $(selector).data()[foo] to grab whatever data is keyed to 'foo' in the data associated with whatever is pointed at by the selector.

Resources