Related
I have an svg that is generated using pygal library and I want to change the value of an array that is defined inside the script tag of the svg content. This is how I am able to read the content of the svg
svgContent = document.querySelector(".myChart").contentDocument
// under <defs>, the second child contains the script tag. Used eval to parse the js content
svgScriptContent = eval(svgContent.querySelector("defs").children[1].innerHTML)
Below is the content of the svg
< script xmlns = "http://www.w3.org/2000/svg"
type = "text/javascript" > window.pygal = window.pygal || {};
window.pygal.config = window.pygal.config || {};
window.pygal.config['f2a1b172-9f14-4f9a-aba9-eff7e05225a3'] = {
"dynamic_print_values": false,
"truncate_label": null,
"inner_radius": 0,
"print_values": false,
"xrange": [0, 100],
"box_mode": "extremes",
"title": "Correlate: income need (x) vs expenditure (y)",
"legend_at_bottom_columns": null,
"height": 560,
"legend_at_bottom": true,
"show_legend": false,
"show_dots": true,
"explicit_size": false,
"y_labels_major": [0, 60, 120],
"show_minor_x_labels": false,
"width": 900,
"force_uri_protocol": "https",
"half_pie": false,
"style": {
"major_label_font_family": "Roboto",
"title_font_family": "Roboto",
"stroke_opacity": ".8",
"legend_box_size": 19,
"legend_font_family": "Roboto",
"colors": ["#FF4A6E", "#58CABF", "#47A4D1", "#ca8658", "#FFA54D"],
"legend_font_size": 19,
"no_data_font_family": "Roboto",
"major_label_font_size": 19,
"value_background": "rgba(229, 229, 229, 1)",
"ci_colors": [],
"label_font_family": "Roboto",
"label_font_size": 19,
"tooltip_font_size": 25,
"value_font_size": 19,
"font_family": "Roboto",
"opacity": ".6",
"foreground": "rgba(0, 0, 0, .87)",
"plot_background": "#FFFFFF",
"x_labels_major_count": 3,
"value_label_font_size": 19,
"value_label_font_family": "Roboto",
"background": "#FFFFFF",
"no_data_font_size": 64,
"value_colors": [],
"guide_stroke_dasharray": "4,4",
"transition": "150ms",
"foreground_subtle": "rgba(0, 0, 0, .54)",
"value_font_family": "Roboto",
"tooltip_font_family": "Roboto",
"title_font_size": 25,
"y_labels_major_count": 3,
"stroke_opacity_hover": ".9",
"major_guide_stroke_dasharray": "6,6",
"opacity_hover": ".8",
"foreground_strong": "rgba(0, 0, 0, 1)"
},
"x_label_rotation": 0,
"fill": false,
"missing_value_fill_truncation": "x",
"zero": 0,
"margin_right": null,
"interpolation_parameters": {},
"x_labels_major": [0, 50, 100],
"rounded_bars": null,
"title_spacing": 7,
"show_y_labels": true,
"dots_size": 2.5,
"legend_spacing": 17,
"show_y_guides": true,
"show_minor_y_labels": false,
"x_labels": null,
"y_title": null,
"order_min": null,
"formatter": null,
"disable_xml_declaration": false,
"strict": false,
"css": ["file://style.css", "file://graph.css"],
"tooltip_fancy_mode": true,
"defs": [],
"show_only_major_dots": false,
"y_label_rotation": 0,
"show_x_labels": true,
"min_scale": 4,
"show_x_guides": true,
"spacing": 10,
"x_labels_major_count": null,
"pretty_print": false,
"tooltip_border_radius": 0,
"interpolate": null,
"js": ["//kozea.github.io/pygal.js/2.0.x/pygal-tooltips.min.js"],
"y_labels": null,
"stroke_style": {
"width": 2
},
"print_labels": false,
"interpolation_precision": 250,
"x_title": null,
"y_labels_major_every": null,
"logarithmic": false,
"legends": ["var1", "var2", "var3", "var4", "var5"],
"max_scale": 16,
"print_zeroes": true,
"no_data_text": "No data",
"truncate_legend": null,
"x_labels_major_every": null,
"secondary_range": null,
"legend_box_size": 12,
"no_prefix": false,
"stroke": false,
"stack_from_top": false,
"include_x_axis": false,
"range": [0, 120],
"classes": ["pygal-chart"],
"margin_top": null,
"margin_left": null,
"y_labels_major_count": null,
"margin_bottom": null,
"inverse_y_axis": false,
"margin": 20,
"allow_interruptions": false,
"print_values_position": "center"
} < /script>
As you can see (towards the end), there is an attribute called legend which is an array containing certain values
"legends": ["var1", "var2", "var3", "var4", "var5"]
I can access the value of the existing key by doing something like this
tooltipLegends = svgScriptContent.legends
console.log(tooltipLegends)
Now I want to replace the array with another array of values and this is what I do
updatedTooltipLegends = ["var6", "var7", "var8", "var9", "var10"]
svgScriptContent.legends = updatedTooltipLegends
But the content of the script tag doesn't get updated. What am I doing wrong?
Another idea is that the content can be read as text and regex can be used to replace the array but not sure how do I do that and if that's a great idea.
The contents of the script tag are code. When the script tag is encountered, that code is executed and it creates an object (with nested objects and arrays) and assigns it to window.pygal.
Your code reads the code of the script and runs it, which is almost certainly unnecessary and probably undesireable. The code has presumably already been run, and run in the correct context (it looks like your .myChart element is an HTMLObjectElement or iframe or similar, given your use of contentDocument). In contrats, your eval runs it in your window, not the window it appears to be intended to run in.
Instead, modify the object that the code created originally. The code's put it on the pygal property of the window in which it ran, so you should be able to do this:
document.querySelector(".myChart").contentWindow.pygal.config['f2a1b172-9f14-4f9a-aba9-eff7e05225a3'].legends = ["var6", "var7", "var8", "var9", "var10"];
If you don't know what GUID will be used, and if there's always only one, you can do this:
const pygal = document.querySelector(".myChart").contentWindow.pygal;
pygal.config[Object.keys(pygal.config)[0]].legends = ["var6", "var7", "var8", "var9", "var10"];
I have a large three.js code, and when I add a simple cylinder to it it crashes the renderer:
const lineGeometry = new Three.CylinderBufferGeometry(1.0, // radiusTop
1.0, // radiusBottom
1.0, // height
7, 1) // radial/height segments
this.line = new Three.Mesh(lineGeometry, material)
this.add(this.line)
// ... later ...
viewer.render()
The crash is during render, in WebGLRenderer.js in renderBufferDirect (actually renderer.setIndex), and seems to be caused by the fact that this geometry has an index property. The code in three.js WebGLRenderer.js where it crashes is this:
if ( index !== null ) {
attribute = attributes.get( index ); // this returns undefined
renderer = indexedBufferRenderer;
renderer.setIndex( attribute ); // attribute is undefined here, causes crash
}
and the failure is due to attribute being undefined. The geometry itself only has attributes position, normal and uv, but I'm not sure that's why it's failing.
I tried to cut this down, but of course in a small example it works fine. :-(
It looks like index is a valid set of indices into the position array:
index: Uint16BufferAttribute {name: "", array: Uint16Array(84), itemSize: 1, count: 84, normalized: false, …}
attributes:
position: Float32BufferAttribute {name: "", array: Float32Array(138), itemSize: 3, count: 46, normalized: false, …}
normal: Float32BufferAttribute {name: "", array: Float32Array(138), itemSize: 3, count: 46, normalized: false, …}
uv: Float32BufferAttribute {name: "", array: Float32Array(92), itemSize: 2, count: 46, normalized: false, …}
so I'm not sure what attributes.get(index) is supposed to be doing, and what I need to feed it so it'll work. Any help appreciated!
This is with three.js r111 btw.
Well, I figured it out, sort of. My lineGeometry has a duplicate buffergeometry.id, so the it gets skipped in WebGLObjects.update() and never updated. As for how I got a duplicate ID, that's still to be figured out.
I'd like to know if it's possible to use vis.js' Timeline chart to build an interface intended to put some keyframes from a <video> element.
I've found some other libraries closer to what I'm looking for, such as timeline.js, or tweentime, but they're more tied as an animation interface, and way too overkill for my needs. I just need to put keyframes with (t,x,y) values and show them in a friendly way.
The thing is that vis.js sets the 'start' and 'end' of the timeline as Date objects, but what I need is something dateless, just from 0 to 'n' seconds. I have not much idea about how achieving this with Date, as this is the approach vis.js uses.
Any ideas about how to build this, or perhaps an alternative I haven't been able to find more close to my approach?
The Timeline of vis.js is built in a modular way, so you could create a version of the Timeline that uses an other axis than the current TimeAxis. It will require to dive into the code though.
You can use the timeline with the following configuration that is explicitly shared in the jsfiddle : https://jsfiddle.net/supereye/7j02Legx/9/
It will provide you an interface such as :
You can use the options below for this setup :
var start = new Date(1970, 0, 1, 0, 0, 0, 0);
var end = new Date(1970, 0, 1, 0, 1, 0, 0);
var options = {
width: "500px",
height: "100px",
align: "box",
groupHeightMode:'fixed',
maxMinorChars : 2,
zoomKey: "ctrlKey",
horizontalScroll: true,
verticalScroll: true,
orientation: "top",
moveable: true,
zoomable : true,
editable: true,
min: start,
max: end,
start: start,
end: end,
zoomMax: 10000000,
zoomMin: 1000,
stack:false,
multiselect: true,
multiselectPerGroup: true,
editable: {
add: true,
updateTime: true,
updateGroup: true,
remove: true,
overrideItems: true
},
onRemove: function(item, callback) {
callback(item);
},
onMove: function(item, callback) {
callback(item);
},
onMoving: function(item, callback) {
let currentItem = timeline_items.get(item.id);
if (item.start < limit_min) item.start = limit_min;
callback(item);
},
rollingMode: {
follow: false,
offset: 0.5
},
};
I have a series of 4 images which I'm attempting to place at specified coordinates in fabricjs, and I'm getting them placed inconsistently sometimes when the page loads. Refreshing once or twice will usually give the correct layout. Trying to prevent this from happening.
Anybody know how to solve this? Here's my code:
var objects = [
{ type: "image", filename : "tv.png" , x: 688, y: 184, angle: 0, zIndex: 10 },
{ type: "image", filename : "polaroid.jpg" , x: 347, y: 515 },
{ type: "image", filename : "polaroid.jpg" , x: 138, y: 643 },
{ type: "image", filename : "polaroid.jpg" , x: 429, y: 803 },
{ type: "text", text: "Your favorite band", x: 1168, y: 1163, angle: -17, canvasRef: null,zIndex: 50 }
];
for (var i=0; i<objects.length;i++){
var objRef = objects[i];
switch(objRef.type){
case 'image':
var url = "img/" + objRef.filename;
fabric.Image.fromURL(url, function(img) {
var objRef = objects[curObject];
img.set({
left: objRef.x,
top: objRef.y,
angle: objRef.angle,
hasBorders: false,
hasControls: false,
hasRotatingPoint: false,
lockMovementX: true,
lockMovementY: true
});
canvas.add(img).renderAll();
img.moveTo(objRef.zIndex);
curObject++;
//canvas.setActiveObject(img);
});
break;
case 'text':
var text = objRef.text;
var fabricText = new fabric.Text(text, {
left: objRef.x,
top: objRef.y,
angle: objRef.angle,
hasBorders: false,
hasControls: false,
hasRotatingPoint: false,
lockMovementX: true,
lockMovementY: true
});
objRef.canvasRef = fabricText;
addTextListeners(fabricText);
canvas.add(fabricText);
break;
}
}
I should mention also that none of this code happens until after window.ready, and after all the images that fabric is attempting to load to canvas have been preloaded using the imagesloaded plugin.
I should also mention that I've tried delaying the loading of each successive image using setTimeout between each load (instead of a loop), and saving the canvas.renderAll() until after the loop, with no success. Even tried running a loop to re-position the items after they were placed on screen. Below are images of the issue - correct, and incorrect, respectively.
It looks like you have a race condition from the images loading in different orders.
The fabric.Image.fromURL() method is using a callback function, which fires after the image has loaded, so you're not guaranteed to have that inner function fire in the same order you called it. But you're using curObject to increment upwards each time the function is called, which assumes they're called in order. And the objRef can get re-assigned to a new object by the time the image loads, which would be why the x/y/rotate values are off (using the values from the other objects in the array).
So, instead of using img.set() after the image had loaded, use the third parameter of the Image.fromURL() method to pass in your attributes:
for (var i=0; i<objects.length;i++){
var objRef = objects[i];
switch(objRef.type){
case 'image':
var url = "img/" + objRef.filename;
fabric.Image.fromURL(url, function(img) {
canvas.add(img).renderAll();
}, {
left: objRef.x,
top: objRef.y,
angle: objRef.angle,
hasBorders: false,
hasControls: false,
hasRotatingPoint: false,
lockMovementX: true,
lockMovementY: true
});
break;
case 'text':
// ...
break;
}
}
So the key to the answer is async call back. Just check when the Alert message called out.
fabric.Image.fromURL('hulk.png', function (img) {
alert('came1');
canvas.add(img).renderAll();
}, {
id: 'hulkid',
num: 1,
left: 10,
top: 10,
angle: 0
});
fabric.Image.fromURL('hulk.png', function (img) {
alert('came2');
canvas.add(img).renderAll();
}, {
id: 'hulkid',
num: 2,
left: 25,
top: 25,
angle: 0
});
alert('came Last');
I found out the way with setTimeout function.
I tested with MidnightLightning's sample
But still has some problem in image z-index in canvas(all settings are good). I think sometimes fabric.Image.fromURL callback function is waiting the image loading, so there would some mistakes in queue of drawing image.
So I added some timeout to function, like that.
var count_layer=0;
var image_src_list_array=['1.jpg', '2.jpg'];
function paint_layer(){
console.log(image_src_list_array[count_layer]);
if(image_src_list_array[count_layer]!=''){
fabric.Image.fromURL(
image_src_list_array[count_layer],
function(img){
console.log('rendering');
fabric_canvas.add(img).renderAll();
}, {
id: count_layer+1,
left: ...,
top: ...,
angle: ...,}
);
count_layer++;
setTimeout(paint_layer,3000);
}
}
Then the console log result is:
1.jpg
rendering
2.jpg
rendering
Before it outputs
1.jpg
2.jpg
rendering
rendering
I'm running this script on a page which shows a box with more information when you roll over it.
site for review
The script works fine, except theres a flicker of the box before it actually scales.
What is causing this? I use the same thing in the main navigation with the same flicking.
Any ideas whats causing this?
//work page springing box
$$('.box').each(function(s) {
var more = $(s).down(2);
$(s).observe('mouseenter', function(e) {
$(more).show();
new Effect.Scale(more, 100, {
scaleX: false,
scaleY: true,
scaleContent: false,
scaleFrom: 1,
mode: 'absolute',
duration: 0.5
});
});
$(s).observe('mouseleave', function(e) {
new Effect.Fade(more, {
duration: 0.2
})
});
});
Thanks.
Rich
I should note, I am testing in Safari 4.0.4
#Allen is correct. When you call $(more).show(); The entire box is shown. Then, when you call new Effect.Scale(more the box is scalled down and slide in. So $(more).show(); is what's causing the flickering. You could try:
$(more).show.bind(more).delay(0.01);
new Effect.Scale(more, 100, {
scaleX: false,
scaleY: true,
scaleContent: false,
scaleFrom: 1,
mode: 'absolute',
duration: 0.5
})
The site looks fine to me. I did notice a very little something, but it could be my imagination.
new Effect.Scale(more, 100, {
scaleX: false,
scaleY: true,
scaleContent: false,
scaleFrom: 1,
mode: 'absolute',
duration: 0.5
});
$(more).show();
You may want to try this though, it seems to show it, then update it, as the code says. Update it first, then show it.
Firefox, fully updated btw.