In backbone.js, a view's render function generates unattached html which can be later attached to the dom.
Currently, I have to have an existing target in the HTML for me to append a svg to. I then use the data/enter pattern to insert elements into the svg. Is there a way to get d3.js to generate the svg without attaching it to the dom?
var svg = d3.select("#target").append('svg')
.attr("viewBox","0 0 100 100");
svg.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("r", 10)
.style("fill", "black");
Alternatively, is it possible to provide d3 with a unattached dom element to append stuff to? Something like this? D3.js documentation suggests that select can accept nodes, but the following does not work for me either
var svg = d3.select(this.$el).append('svg') // Uncaught TypeError: Object [object Object] has no method 'appendChild'
.attr("viewBox","0 0 100 100");
svg.selectAll("circle")
.data([1,2,3])
.enter().append("circle")
.attr("r", 10)
.style("fill", "black");
I found the answer in this post SVG not rendering properly as a backbone view
d3.select(this.el)
does the trick!
Related
I am creating a simple react app (HTML) that allows a user to browse to an image on their local pc, and then displays it in an image tag. I want to take a data-url and dynamically create a hidden canvas tag (open to a different approach, but I want to resize the image, not set size contraints on the tag displaying the image). Here is my resize code
MyComponent.res (offending code)
let resize = dataUrl => {
let canvas = React.createElement(_ => React.string("canvas"), {"style": "display: none;"})
canvas.getContext("2d");
dataUrl
}
The error
We've found a bug for you!
/Users/n0321437/projects/rescript-react/from-local-template/src/Upload.res:14:12-21
12 │ let canvas = React.createElement(_ => React.string("canvas"), {"sty
│ le": "display: none;"})
13 │ //let myCanvas = canvas([React.null])
14 │ canvas.getContext("2d");
15 │ dataUrl
16 │ }
The record field getContext can't be found.
I haven't found a much documentation or postings on using createElement or createElementVariadic - so I am guessing here. It looks as though createElement returns an object of type element but there are no associated methods:
React.res
type element
external createElement: (component<'props>, 'props) => element = "createElement"
So I guess there are a couple of questions
Have i infact created an element that would represent the HTML Object of Canvas?
If I have done so, how do I call methods on that code?
If I have not, how do I create a hidden Canvas object?
FInally how might one navigate the documentation and source to discover this on their own?
React is a library that provides a declarative API for creating, inserting and updating the DOM by way of a virtual DOM. React.createElement is an internal API used to create virtual DOM elements.
What you seem to want is to create an actual DOM element, and then NOT insert it into DOM. React is not designed to do this.
Instead what you want is bindings to the DOM API, which you could make yourself if you only need a few of its features, or you could use a full-blown set of DOM bindings such as rescript-webapi, which conveniently also includes bindings for Canvas.
This is how you'd create a canvas element:
open Webapi.Dom
let canvas = document->Document.createElement("canvas")
then to get the canvas context you'd use:
open Webapi.Canvas
let context = canvas->CanvasElement.getContext2d
then use the functions in Canvas2d to do what you want:
let image = context->Canvas2d.createImageDataCoords(~width=100, ~height=100)
...
context->Canvas2d.putImage(image, ~dx=0, ~dy=0)
In my data for force layout I have
"nodes":[
{"id":"0", "name":"A",
"isListedIn":["USSDN","Canadian list"]
},
Normally, I would then bind it to D3 selection such as
d3.select(body).selectAll(".node").data(graph.nodes).enter()
.append("image")
.attr("name") function(d) return d.name)
.attr("isListedIn",function(d) return d.isListedIn)
Where I try to get it out
var listList=d3.select("#listList").append("ul")
.data(listList)
.enter()
.append("li")
.text(function(d){return d})
But d.isListedIn is not working. The thing I tried to do is when user click on 1 node--> I can get the value of isListedIn out as a array. How can I achieve this?
What exactly are you trying to achieve?
It doesn't really makes sense to add an attribute to store the isListedIn list on your nodes just to retrieve it later, as d3 already does that for you. If you want to get the data for a DOM element, you can use either d3.select('#el').data()[0]['isListedIn'] or d3.select('#el').each(function(d) { console.log(d.isListedIn); }); depending on what you want to do with it.
I'm attempting to bind SVG attributes using AngularJS. I have the following code for a resize handle of a shape:
// S handle.
var s_handle = d3.select(document.createElementNS("http://www.w3.org/2000/svg", "rect"))
.attr("id", "s-resize")
.attr("x", "{{(objects['" + id + "'].w - (scope.dragHandle.width / 2))}}")
.attr("y", "{{(objects['" + id + "'].h - (scope.dragHandle.width / 2))}}")
.attr(scope.dragHandle)
.attr("cursor", "s-resize")
.call(sresize);
$compile(s_handle.node())(scope);
element.append(s_handle);
When I run this code, I get the following error:
Error: Invalid value for <rect> attribute x="{{(objects['9b320170-6365-4f40-801d-a7a5c6b936f9'].w - (scope.dragHandle.width / 2))}}" d3.min.js:1
Error: Invalid value for <rect> attribute y="{{(objects['9b320170-6365-4f40-801d-a7a5c6b936f9'].h - (scope.dragHandle.width / 2))}}" d3.min.js:1
I tried removing the append step to isolate the issue - and it looks like the issue actually happens when I set the x and y attributes, even before the Angular compile stage.
Note that I have this method working with another SVG object using the transform attribute, which natively accepts a string.
Has anyone run into this problem before? Are there any solutions? Thank you.
Edit: Been stepping through and it looks like $compile` isn't actually changing the element at all. Could this be because the element is invalid?
Looks like this was related to this issue: https://github.com/angular/angular.js/pull/2061
It was solved in that issue as well.
SVG elements use an XML-based schema, and are namespaced, and as such have validation rules. Certain attributes can't take an Angular interpolated directive because they expect a numeric value, and the browser validates this before Angular ever has a chance to catch it (even if the element is created and not appended to the DOM yet).
If anyone runs into this issue, hopefully this answer will help.
I'm creating a barchart with d3 where the data is in a backbone collection.
I want the user to be able to interact with the bars in the chart, selecting them, editing data, etc. etc.
I figured the best way to do this was to create a view for the chart, and a separate view for the bars.
in my chart view, I have
create_bar: function(){
var chart = d3.select("div#chart");
timeline.selectAll("div")
.data(Myapp.chart.models)
.enter()
.append(function(d){console.log(d);
var bar = new Myapp.Views.ChartBar({model:d});
return bar.el;
});
}
but unfortunately, it looks like append fails with a function.
I'm looking at putting some moderately complex html within the bar div, as well as a few data-points.
Any suggestions on how to do this?
Backbone views with d3 make kind of an odd mix, but here's one way it might work:
timeline.selectAll("div")
.data(Myapp.chart.models)
.enter()
.append('div')// or 'span' or even Myapp.Views.ChartBar.prototype.el
.each(function(d, i) {
var bar = new Myapp.Views.ChartBar({model:d});
bar.setElement(this);// Here "this" is the dom element
}
EDIT:
The last 2 lines can be combined into a single one, skipping the call to setElement():
var bar = new Myapp.Views.ChartBar({model:d, el:this});
So I am pulling in a template in the render() method in BackBone.
var html = $(template).render();
this.el.html(html);
I have some css defined for that template, for instance a percent width on an element. I can't seem to get that height using jquery after the lines above. So does that actually render that to DOM, when do elements get calculated?
Thx
Ralph
First, you need to use the jquery cached element this.$el.html(html).
After this the view it's already rendered, but not attached to the DOM, hence, properties like height or width will be undefined (or zero).
For example, this should return something:
var html = $(template).render();
this.$el.html(html);
this.$el.appendTo("body");
console.log(this.$el.height());