Highcharts Scrollbar up arrow throws error on click - svg

When clicking the up arrow on a highcharts xrange graph with a scrollbar on the yAxis (there is also one on the xAxis but it doesn't throw an error when i use it's arrow button)
the error returned is
Uncaught TypeError: Cannot read property 'call' of undefined
at SVGGElement.<anonymous> (highcharts.src.js:5182)
I haven't found anything specific on the internet about this error so far but if i do i will update this ticket
Might this have something to do with having an X and Y scrollbar ?
I don't plan on using the button at all since we have implemented a listener for the trackpad/wheel - is there a way of removing the arrow button entirely (rather than make it transparent which i currently have implemented which includes an unsightly hover i can't get rid of)
If you can help me A) Remove/hide the button entirely B) listen for the click and throw away the event to avoid this error or C) fix whatever the underlying cause of this error is ... i'd greatly appreciate it
{
'chart': {
'renderTo': graphId,
'type': 'xrange',
'zoomType': 'xy',
'panning': true,
'panKey': 'shift',
'marginRight': 40,
'marginLeft': 150,
'resetZoomButton': {
'position': {
'x': -150,
'y': -10
}
}
},
'exporting': {
'enabled': true,
'buttons': {
'enabled': true,
'contextButton': {
'enabled': false
},
'resetScopeButton': {
'y': -10,
'x': -25,
'symbolX': 20,
'symbolY': 20,
'enabled': true,
'onclick': context['LiAnalytics']['resetScopeButton'],
'symbol': 'url(../images/refresh.png)'
},
'hourButton': {
'enabled': true,
'text': 'H',
'y': -10,
'x': -50,
'onclick': context['LiAnalytics']['hourButton']
},
'dayButton': {
'text': 'D',
'y': -10,
'x': -75,
'enabled': endTS - startTS > 86400000,
'onclick': context['LiAnalytics']['dayButton']
},
'weekButton': {
'text': 'W',
'y': -10,
'x': -100,
'enabled': endTS - startTS >= 604800000,
'onclick': context['LiAnalytics']['weekButton']
},
'monthButton': {
'text': 'M',
'y': -10,
'x': -125,
'enabled': endTS - startTS >= 2419000000,
'onclick': context['LiAnalytics']['monthButton']
}
}
},
'legend': {
'enabled': false
},
'xAxis': {
'type': 'datetime',
'dateTimeLabelFormats': {
...
},
'events': {
'setExtremes': new js.JsFunction.withThis(_handleRedraw)
},
'min': (endTS - startTS) > initialZoom ? endTS - initialZoom : startTS,
'max': endTS,
'scrollbar': {
'enabled': true,
'showFull': false,
'barBackgroundColor': '#ccc',
'barBorderRadius': 7,
'barBorderWidth': 0,
'buttonBorderWidth': 0,
'buttonArrowColor': 'transparent',
'buttonBackgroundColor': 'transparent',
'rifleColor': 'transparent',
'trackBackgroundColor': '#F3F3F3',
'trackBorderColor': 'transparent',
'height': 10,
'minWidth': 25
}
},
'yAxis': {
'categories': agents,
'min': 0,
'max': agents.length < maxY ? agents.length - 1 : maxY,
'scrollbar': { /* Why you throw err on click ? */
'enabled': true,
'showFull': false,
'barBackgroundColor': '#ccc',
'barBorderRadius': 7,
'barBorderWidth': 0,
'buttonBorderWidth': 0,
'buttonArrowColor': 'transparent', /* Remove entirely ? */
'buttonBackgroundColor': 'transparent',
'rifleColor': 'transparent',
'trackBackgroundColor': '#F3F3F3',
'trackBorderColor': 'transparent',
'height': 10,
'minWidth': 25
},
'reversed': true,
'tickmarkPlacement': 'on',
'gridLineColor': 'transparent'
},
'plotOptions': {
'series': {
'animation': {
'duration': 2000
},
'point': {
'events': { /* can i do something similar for scrollbar? */
'mouseOver': new js.JsFunction.withThis(_mouseOver),
'mouseOut': new js.JsFunction.withThis(_mouseOut)
}
},
'pointWidth': 20,
'pointPlacement': 0,
'minPointLength': 10,
'borderRadius': 0
}
},
'series': series,
'tooltip': {
...
}
}
(C) Current code block
(function() {
//internal functions
function stopEvent(e) {
if (e) {
if (e.preventDefault) {
e.preventDefault();
}
if (e.stopPropagation) {
e.stopPropagation();
}
e.cancelBubble = true;
}
}
/* Wrap allows us to override the behavior of render while not interrupting the normal rendering procedure */
Highcharts.wrap(Highcharts.Chart.prototype, 'render', function(proceed) {
var chart = this;
proceed.call(chart);
/* When a chart has a scrollbar and is xrange our PM/UX has requested the touchpad be abled to control the scrollbar */
if (chart.options['chart']['type'] === "xrange" && chart.options['yAxis'][0]['scrollbar']['enabled']) {
// Add the mousewheel event
Highcharts.addEvent(chart.container, document.onmousewheel === undefined ? 'DOMMouseScroll' : 'mousewheel', function (event) {
var delta, diff, extr, newMax, newMin, step, axis = chart.yAxis[0];
e = chart.pointer.normalize(event);
// Firefox uses e.detail, WebKit and IE uses wheelDelta
delta = e.detail || -(e.wheelDelta / 120);
delta = delta < 0 ? 1 : -1;
/* Up or Down */
if (chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) {
extr = axis.getExtremes();
if (extr.dataMax !== extr.dataMin) {
diff = extr.max - extr.min;
step = diff / 5;
/* move by fifths */
step = step > 1 ? Math.ceil(step) : 1;
/* Min step is 1, Move by whole numbers */
step = step * delta;
/* Up/Down */
if (step > 0) {
/* UP */
if (extr.max + step > extr.dataMax) {
newMax = extr.dataMax;
newMin = extr.dataMax - diff;
/* Enforce window not getting too small */
} else {
newMin = extr.min + step;
newMax = extr.max + step;
}
} else {
/* DOWN */
if (extr.min + step < 0) {
newMin = 0;
newMax = diff;
} else {
newMin = extr.min + step;
newMax = extr.max + step;
}
}
axis.setExtremes(newMin, newMax, true, false);
}
}
stopEvent(event);
return false;
});
}
});
Highcharts.Scrollbar.prototype.addEvents = function() {
var chart = this;
var buttonsOrder = chart.options.inverted ? [1, 0] : [0, 1],
buttons = chart.scrollbarButtons,
bar = chart.scrollbarGroup.element,
track = chart.track.element,
mouseDownHandler = chart.mouseDownHandler,
mouseMoveHandler = chart.mouseMoveHandler,
mouseUpHandler = chart.mouseUpHandler,
_events;
// Mouse events
_events = [
/* [buttons[buttonsOrder[0]].element, 'click', this.buttonToMinClick],
[buttons[buttonsOrder[1]].element, 'click', this.buttonToMaxClick], */
[track, 'click', this.trackClick],
[bar, 'mousedown', mouseDownHandler],
[bar.ownerDocument, 'mousemove', mouseMoveHandler],
[bar.ownerDocument, 'mouseup', mouseUpHandler]
];
// Touch events
if (Highcharts.hasTouch) {
_events.push(
[bar, 'touchstart', mouseDownHandler], [bar.ownerDocument, 'touchmove', mouseMoveHandler], [bar.ownerDocument, 'touchend', mouseUpHandler]
);
}
// Add them all
_events.forEach(function(args) {
Highcharts.addEvent.apply(null, args);
});
chart._events = _events;
};
}());
EDIT: removed old details

This was caused by trying to singularly disable the context button while including other custom buttons. The context button hung around as a phantom button that was overladed over the scrollbar up arrow causing the misdiagnosis during debugging.
Solution i ended up going up with was repurposing the context button by overriding the symbol/symbolX symoblY(to center symbol in button area)/onclick behavior
routed onclick to the resetScope behavior i was working toward
overrode the burger symbol with the png saved / decided on by UX

Related

How do I add an object next to a moving object in Phaser?

In the snippet below, I place two red squares next to each other. You can see there is no gap between them.
If I try to do the same thing with moving objects, there is a slight gap. What's the right way to do this?
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.min.js"></script>
</head>
<body>
<script>
var config = {
type: Phaser.AUTO,
width: 800,
height: 600,
physics: { default: 'arcade' },
scene: { create: create, update: update },
};
var game = new Phaser.Game(config);
var red, red2, yellow, yellow2;
const velocity = -300;
function create() {
red = this.add.rectangle(400, 200, 100, 100, 0xff0000).setOrigin(0, 0);
red2 = this.add.rectangle(red.x + 100, 250, 100, 100, 0xff6666).setOrigin(0, 0);
yellow = this.add.rectangle(400, 400, 100, 100, 0xffff00).setOrigin(0, 0);
this.physics.add.existing(yellow);
yellow.body.velocity.x = velocity;
}
function update() {
if (yellow.x < 200 && !yellow2) {
yellow2 = this.add.rectangle(yellow.x + 100, 450, 100, 100, 0xffff66).setOrigin(0, 0);
this.physics.add.existing(yellow2);
yellow2.body.velocity.x = velocity;
}
if (yellow.x < -200) {
this.scene.restart();
yellow2 = undefined;
}
}
</script>
</body>
</html>
I think the solution is: Use the position of the physics body by changing yellow.x + 100 to yellow.body.x + 100.
(Or maybe yellow.body.position.x + 100? Not sure what the difference is, or if body.x is just an alias for body.position.x.)
Related: https://phaser.discourse.group/t/lag-in-dom-sprite-position-updates/798
A easy way out is to use the Phaser function: Phaser.Display.Align.To.RightCenter and the event: postupdate.
You then just have to calculate the offset between the objects and add pass it into the function. ( documentation to the Align function). this is based on this solution: phaser forum
canvas{
transform: translate(-40%, -40%) scale(.3);
}
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.min.js"></script>
</head>
<body style="overflow:hidden">
<script>
var config = {
type: Phaser.AUTO,
width: 800,
height: 600,
physics: { default: 'arcade' },
scene: { create: create, update: update },
};
var game = new Phaser.Game(config);
var red, red2, yellow, yellow2;
const velocity = -300;
var yOffSet = 0;
function create() {
red = this.add.rectangle(400, 200, 100, 100, 0xff0000).setOrigin(0, 0);
red2 = this.add.rectangle(red.x + 100, 250, 100, 100, 0xff6666).setOrigin(0, 0);
yellow = this.add.rectangle(400, 400, 100, 100, 0xffff00).setOrigin(0, 0);
this.physics.add.existing(yellow);
yellow.body.velocity.x = velocity;
this.events.on("postupdate", function () {
if (yellow && yellow2) {
Phaser.Display.Align.To.RightCenter(yellow2, yellow, 0, yOffSet);
}
});
}
function update(time, delta) {
if (yellow.x < 200 && !yellow2) {
yellow2 = this.add.rectangle(yellow.x + 100, 450, 100, 100, 0xffff66).setOrigin(0, 0);
this.physics.add.existing(yellow2);
yellow2.body.velocity.x = velocity;
yOffSet = yellow2.y - yellow.y
}
if (yellow.x < -200) {
this.scene.restart();
yellow2 = undefined
}
}
</script>
</body>
</html>
Alternativly you could, calculate the x position more exact, but it is no easy since, the update function is not called in the same time interval. This could cause minor overlap or splitting.
// deltaTime being the the second paramenter of the update function
yellow2 = this.add.rectangle(yellow.x + 100 + (velocity * deltaTime), 450, 100, 100, 0xff0000).setOrigin(0, 0);
Or even easier, you could simply set the x in the postUpdate event (without Align)
this.events.on("postupdate", function () {
if ( yellow2) {
yellow2.x =yellow.x + 100;
}
});

Proper way to approach loadable masks in Fabric.js

I have this bounty open Fabricjs mask object with transformation when trying to mask objects with Fabric.js.
The tool I'm developing should allow users to draw a mask over image objects, and apply transformations (skew scale rotate etc) to this object before or after the mask. I'm close to obtaining this result but objects with an angle are still not working.
I'm also trying to save this object to a database using toJSON and loadFromJSON, but after a few days trying to accomplish this I realize that this solution will not work because any references outside the ctx scope can't be accessed while loading from JSON, so they throw an error.
clipTo: function(ctx) {
mask.set({
left:
-object.width / 2 -
(mask.width / 2) * originalMaskScaleX -
originalObjLeft / originalObjScaleX,
top:
-object.height / 2 -
(mask.height / 2) * originalMaskScaleY -
originalObjTop / originalObjScaleY,
objectCaching: false
});
mask.render(ctx);
}
Is Fabric.js the proper solution to this problem? Should I be using something else? If this can be done with Fabric.js, what is the proper approach?
I extended fabric.Image with some custom attributes.
Also I attached the mask on fabric.Image.
For fabric.Image.fromObject after the image is loaded I need it to load also the mask( which I know is a path) and attach to image.
This is a fast implementation. I'm pretty sure this code can be simplified.
Please tell me know if something is not clear enougth
canvas = new fabric.Canvas("canvas", {
backgroundColor: "lightgray",
width: 1280,
height: 720,
preserveObjectStacking: true,
selection: false,
stateful: true
});
canvas.isDrawingMode = true;
canvas.freeDrawingBrush.color = "black";
canvas.freeDrawingBrush.width = 2;
canvas.on("path:created", function(options) {
clip(options.path);
});
function clip(path) {
canvas.isDrawingMode = false;
canvas.remove(path);
let mask = new fabric.Path(path.path, {
top: object.top,
left: object.left,
objectCaching: false,
strokeWidth: 0,
scaleX: 1 / object.scaleX,
scaleY: 1 / object.scaleY,
pathOffset: {
x: 0,
y: 0
}
});
object = canvas.getObjects()[0];
object.originalObjLeft = object.left,
object.originalObjTop = object.top,
object.originalMaskScaleX = mask.scaleX,
object.originalMaskScaleY = mask.scaleY,
object.originalObjScaleX = object.scaleX,
object.originalObjScaleY = object.scaleY;
var transformedTranslate = object.translateToGivenOrigin({
x: object.left,
y: object.top
}, object.originX, object.originY, 'center', 'center');
object.originalTransformLeft = transformedTranslate.x - object.getCenterPoint().x;
object.originalTransformTop = transformedTranslate.y - object.getCenterPoint().y;
object.originalAngle = object.angle;
object.clipMask = mask;
object.set({
clipTo: function(ctx) {
ctx.save();
ctx.rotate(-this.originalAngle * Math.PI / 180);
ctx.translate(this.originalTransformLeft / this.originalObjScaleX, this.originalTransformTop / this.originalObjScaleY)
this.clipMask.set({
left: -object.width / 2 - (this.clipMask.width / 2 * this.originalMaskScaleX) - this.originalObjLeft / this.originalObjScaleX,
top: -object.height / 2 - (this.clipMask.height / 2 * this.originalMaskScaleY) - this.originalObjTop / this.originalObjScaleY,
objectCaching: false
});
this.clipMask.render(ctx);
ctx.restore();
}
});
canvas.requestRenderAll();
}
// image
let image = new Image();
image.onload = function() {
object = new fabric.Image(image, {
width: 500,
height: 500,
scaleX: 0.8,
scaleY: 0.8,
angle: 45,
top: 50,
left: 100
});
canvas.add(object);
};
image.src = "http://i.imgur.com/8rmMZI3.jpg";
fabric.util.object.extend(fabric.Image.prototype, {
clipMask: null,
originalObjLeft: 0,
originalObjTop: 0,
originalMaskScaleX: 1,
originalMaskScaleY: 1,
originalObjScaleX: 1,
originalObjScaleY: 1,
originalAngle:0,
originalTransformLeft:0,
originalTransformTop:0
});
fabric.Image.prototype.toObject = (function(toObject) {
return function(propertiesToInclude) {
return fabric.util.object.extend(toObject.call(this, propertiesToInclude), {
clipMask: this.clipMask ? this.clipMask.toObject(propertiesToInclude) : null,
originalObjLeft: this.originalObjLeft,
originalObjTop: this.originalObjTop,
originalMaskScaleX: this.originalMaskScaleX,
originalMaskScaleY: this.originalMaskScaleY,
originalObjScaleX: this.originalObjScaleX,
originalObjScaleY: this.originalObjScaleY,
originalAngle:this.originalAngle,
originalTransformLeft:this.originalTransformLeft,
originalTransformTop:this.originalTransformTop
});
}
})(fabric.Image.prototype.toObject);
fabric.Image.fromObject = (function(fromObject) {
return function(_object, callback) {
fromObject.call(this, _object, (function(callback, _object) {
return function(image) {
if (image.clipMask) {
fabric.Path.fromObject(image.clipMask, (function(callback) {
return function(path) {
path.pathOffset.x = 0;
path.pathOffset.y = 0;
image.clipMask = path;
callback(image);
}
})(callback))
} else {
callback(image);
}
}
})(callback, _object));
return;
}
})(fabric.Image.fromObject)
$("#button1").on('click', function() {
let dataJSON = canvas.toJSON();
canvas.clear();
canvas.loadFromJSON(
dataJSON,
canvas.renderAll.bind(canvas));
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.3.6/fabric.js"></script>
<script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
<button id="button1">SAve/Load JSON</button>
<div class="canvas__wrapper">
<canvas id="canvas" width="1280" height="720"></canvas>
</div>
UPDATE
I updated the code to fix the problem with angle from here:

Fabric.js: using animate method on many object is so slow

I'm using animate method to animate 100 objects, but in my case, the performance is so slow, how do I fix it?
my demo code:
https://jsfiddle.net/cs6jqj2w/
Please take a look at fabricJS demo
Also, I modified little bit that demo using your function for generating random numbers and created 100 shpaes in this fiddle
(function() {
var canvas = this.__canvas = new fabric.Canvas('c');
fabric.Object.prototype.transparentCorners = false;
var Cross = fabric.util.createClass(fabric.Object, {
objectCaching: false,
initialize: function(options) {
this.callSuper('initialize', options);
this.animDirection = 'up';
this.width = 100;
this.height = 100;
this.w1 = this.h2 = 100;
this.h1 = this.w2 = 30;
},
animateWidthHeight: function() {
var interval = 2;
if (this.h2 >= 30 && this.h2 <= 100) {
var actualInterval = (this.animDirection === 'up' ? interval : -interval);
this.h2 += actualInterval;
this.w1 += actualInterval;
}
if (this.h2 >= 100) {
this.animDirection = 'down';
this.h2 -= interval;
this.w1 -= interval;
}
if (this.h2 <= 30) {
this.animDirection = 'up';
this.h2 += interval;
this.w1 += interval;
}
},
_render: function(ctx) {
ctx.fillRect(-this.w1 / 2, -this.h1 / 2, this.w1, this.h1);
ctx.fillRect(-this.w2 / 2, -this.h2 / 2, this.w2, this.h2);
}
});
for (var i = 0; i < 100; i++){
canvas.add(
new Cross({ top: getRandomInt(0,500), left: getRandomInt(0,500)})
);
}
setTimeout(function animate() {
canvas.forEachObject(function(obj){ obj.animateWidthHeight(); obj.dirty = true; });
canvas.renderAll();
setTimeout(animate, 10);
}, 10);
})();
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
UPDATE:
Your animation didn't work because you had to much rendering of the canvas.
You have to generate 99 items without rendering and last one with the rendering. Also, last item has to me with the maximum duration for the animation in order to complete animation for all shapes.
var fabric = window.fabric
var canvas = new fabric.StaticCanvas('c')
function createItem(canvas) {
var item = new fabric.Circle({
left: -100,
top: getRandomInt(0, 500),
opacity: Math.random().toFixed(2),
radius: getRandomInt(10, 50),
})
item.keepGoing = true
canvas.add(item)
// itemTopAnim(canvas, item, getNextTop(item.top))
// itemLeftAnim(canvas, item)
return item;
}
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function getNextTop(top) {
if (top < (canvas.height / 2)) {
return top + getRandomInt(50, 200)
}
return top - getRandomInt(50, 200)
}
function itemTopAnim(canvas, item, top) {
item.animate('top', top, {
duration: getRandomInt(1, 3) * 1000,
// onChange: canvas.renderAll.bind(canvas),
easing: fabric.util.ease.easeInOutCubic,
onComplete: function() {
item.keepGoing && itemTopAnim(canvas, item, getNextTop(item.top))
}
})
}
function itemTopAnimLast(canvas, item, top) {
item.animate('top', top, {
duration: 3 * 1000,
onChange: canvas.renderAll.bind(canvas),
easing: fabric.util.ease.easeInOutCubic,
onComplete: function() {
item.keepGoing && itemTopAnim(canvas, item, getNextTop(item.top))
}
})
}
function itemLeftAnim(canvas, item) {
item.animate('left', canvas.width - item.radius, {
duration: getRandomInt(5, 10) * 1000,
//onChange: canvas.renderAll.bind(canvas),
onComplete: function() {
item.keepGoing = false
}
})
}
function itemLeftAnimLast(canvas, item) {
item.animate('left', canvas.width - item.radius, {
duration: 10 * 1000,
onChange: canvas.renderAll.bind(canvas),
onComplete: function() {
item.keepGoing = true
}
})
}
for (var i = 0; i < 100; i++) {
var item = createItem(canvas);
if (i == 99){
itemLeftAnimLast(canvas, item)
itemTopAnimLast(canvas, item, getNextTop(item.top))
} else {
itemLeftAnim(canvas, item)
itemTopAnim(canvas, item, getNextTop(item.top))
}
}
Check this updated fiddle
Hopefully, it gives you more idea how it works right now.
Sounds like you've got it working now. Just thought I would add that the docs on http://fabricjs.com/fabric-intro-part-2 also mention that you might want to use requestAnimationFrame instead of the onChange callback when animating lots of objects:
The reason animate doesn't automatically re-render canvas after each change is due to performance. After all, we can have hundreds or thousands animating objects on canvas, and it wouldn't be good if every one of them tried to re-render screen. In the case of many objects, you could use something like requestAnimationFrame (or other timer-based) loop to render canvas continuosly on its own, without calling renderAll for each object. But most of the time, you'll probably need to explicitly specify canvas.renderAll as "onChange" callback.
I had similar problems to yours, and this advice worked quite well.

How to resize svg shape based on text content

I would like to have svg shape scale based on text content of text area or text-input. As the text content increases, the size of the underlying svg element should increase as well
This is what I have so far:
var graph = new joint.dia.Graph;
var paper = new joint.dia.Paper({
el: $('#myholder'),
width: 1330,
height: 660,
model: graph,
gridSize: 1,
defaultLink: new joint.dia.Link({
attrs: {'.marker-target': {d: 'M 10 0 L 0 5 L 10 10 z'}}
}),
validateConnection: function (cellViewS, magnetS, cellViewT, magnetT, end, linkView) {
// Prevent linking from input ports.
if (magnetS && magnetS.getAttribute('type') === 'input')
return false;
// Prevent linking from output ports to input ports within one element.
if (cellViewS === cellViewT)
return false;
// Prevent loop linking
return (magnetS !== magnetT);
// Prevent linking to input ports.
return magnetT && magnetT.getAttribute('type') === 'input';
},
// Enable marking available cells & magnets
markAvailable: true,
//Enable link snapping within 75px lookup radius
// snapLinks: {radius: 75},
interactive: function (cellView, methodName)
{
if (cellView.model.get('isInteractive') === false)
return false;
// return true;
}
});
joint.shapes.devs.CircleModel = joint.shapes.devs.Model.extend({
markup: '<g class="rotatable"><g class="scalable"><circle class="body"/></g><text class="label"/><g class="inPorts"/><g class="outPorts"/></g>',
// portMarkup: '<g class="port port<%=1%>"><rect class="port-body"/><text class="port-label"/></g>',
defaults: joint.util.deepSupplement({
type: 'devs.CircleModel',
attrs: {
'.body': {r: 50, cx: 50, stroke: '', fill: 'white'},
'.label': {text: '', 'ref-y': 0.5, 'y-alignment': 'middle'},
'.port-body': {r: 3, width: 10, height: 10, x: -5, stroke: 'gray', fill: 'lightgray', magnet: 'active'}
}
}, joint.shapes.devs.Model.prototype.defaults)
});
joint.shapes.devs.CircleModelView = joint.shapes.devs.ModelView;
var rect = new joint.shapes.basic.Rect({
isInteractive: false,
position: {x: 10, y: 50},
size: {width: 51, height: 41},
attrs: {rect: {fill: '#D6F2FC', stroke: '#7E7E7E'}, '.': {magnet: false}}
});
// Create a custom element.
// ------------------------
joint.shapes.html = {};
joint.shapes.html.Element = joint.shapes.basic.Rect.extend({
defaults: joint.util.deepSupplement({
type: 'html.Element',
attrs: {
rect: {stroke: 'none', 'fill-opacity': 0}
}
}, joint.shapes.basic.Rect.prototype.defaults)
});
// Create a custom view for that element that displays an HTML div above it.
// -------------------------------------------------------------------------
joint.shapes.html.ElementView = joint.dia.ElementView.extend({
template: [
'<div class="html-element">',
'<button class="delete">x</button>',
'<span></span>', '<br/>',
// '<input type="text" value="" />',
'<textarea id="txt" type="text" rows="10" value="Start writing"></textarea>',
'</div>'
].join(''),
initialize: function () {
_.bindAll(this, 'updateBox');
joint.dia.ElementView.prototype.initialize.apply(this, arguments);
this.$box = $(_.template(this.template)());
// Prevent paper from handling pointerdown.
this.$box.find('input,select').on('mousedown click', function (evt) {
evt.stopPropagation();
});
this.$ruler = $('<span>', {style: 'visibility: hidden; white-space: pre'});
$(document.body).append(this.$ruler);
// This is an example of reacting on the input change and storing the input data in the cell model.
this.$box.find('textarea').on('input', _.bind(function (evt) {
var val = $(evt.target).val();
this.model.set('textarea', val);
this.$ruler.html(val);
var width = this.$ruler[0].offsetWidth;
var height = this.$ruler[0].offsetHeight;
var area = width * height;
height = area / 150;
width = 150;
if ((area > 9000))
{
this.model.set('size', {width: width + 50, height: height + 80});
this.$box.find('textarea').css({width: width, height: height + 30});
// this.$box.find('.color-edit').css({width: width + 50, height: height + 80});
this.$box.find('.in').css({top: height + 75});
}
}, this));
this.$box.find('textarea').on('click', _.bind(function () {
this.$box.find('.delete').css({opacity: 1});
this.$box.find('textarea').css({opacity: 1});
}, this));
this.$box.find('textarea').on('blur', _.bind(function () {
this.$box.find('.delete').css({opacity: 0});
this.$box.find('textarea').css({opacity: 0});
}, this));
this.$box.find('.delete').on('click', _.bind(this.model.remove, this.model));
// Update the box position whenever the underlying model changes.
this.model.on('change', this.updateBox, this);
// Remove the box when the model gets removed from the graph.
this.model.on('remove', this.removeBox, this);
this.updateBox();
this.listenTo(this.model, 'process:ports', this.update);
joint.dia.ElementView.prototype.initialize.apply(this, arguments);
},
render: function () {
joint.dia.ElementView.prototype.render.apply(this, arguments);
this.paper.$el.prepend(this.$box);
this.updateBox();
return this;
},
updateBox: function ()
{
// Set the position and dimension of the box so that it covers the JointJS element.
var bbox = this.model.getBBox();
// Example of updating the HTML with a data stored in the cell model.
this.$box.find('label').text(this.model.get('label'));
this.$box.find('span').text(this.model.get('select'));
this.$box.css({width: bbox.width + 6, height: bbox.height, left: bbox.x, top: bbox.y, transform: 'rotate(' + (this.model.get('angle') || 0) + 'deg)'});
},
removeBox: function (evt) {
this.$ruler.remove();
this.$box.remove();
}
});
paper.on('cell:pointerdblclick', function (cellView, evt, x, y)
{
var clone = cellView.model.clone();
if (rect.id === cellView.model.id)
{
clone = new joint.shapes.html.Element({
position: {x: 100, y: 60},
size: {width: 81, height: 69},
inPorts: [''],
outPorts: [''],
attrs: {
'.': {magnet: true},
'.label': {text: '', 'ref-x': .4, 'ref-y': .2},
'.inPorts circle': {type: 'input'},
'.outPorts circle': {type: 'output'},
'.port-body': {r: 3}
}
});
// clone.resize(2*81,2*39)
graph.addCell(clone);
}
});
// // First, unembed the cell that has just been grabbed by the user.
paper.on('cell:pointerdown', function (cellView, evt, x, y) {
var cell = cellView.model;
if (!cell.get('embeds') || cell.get('embeds').length === 0) {
// Show the dragged element above all the other cells (except when the
// element is a parent).
cell.toFront();
_.invoke(graph.getConnectedLinks(cell), 'toFront');
}
if (cell.get('parent')) {
graph.getCell(cell.get('parent')).unembed(cell);
}
});
// When the dragged cell is dropped over another cell, let it become a child of the
//element below.
paper.on('cell:pointerup', function (cellView, evt, x, y) {
if (cellView.model.isLink())
return;
var cell = cellView.model;
var cellViewsBelow = paper.findViewsFromPoint(cell.getBBox().center());
if (cellViewsBelow.length) {
// Note that the findViewsFromPoint() returns the view for the `cell` itself.
var cellViewBelow = _.find(cellViewsBelow, function (c) {
return c.model.id !== cell.id;
});
// Prevent recursive embedding.
if (cellViewBelow && cellViewBelow.model.get('parent') !== cell.id) {
cellViewBelow.model.embed(cell);
}
}
});
graph.addCells([rect]);
Could not find a solution elsewhere. Any help would be appreciated. thanks
You have to make the HTML Input resize based on the text inside.
Auto-scaling input[type=text] to width of value?
The ElementView has to listen to the HTML Input changes (input event) and update the size of the model based on the width and height of the HTML Input.
Example:
function onTextInput(evt) {
var $input = $(evt.target);
// 1. auto-scaling the input based on the text inside.
$input.attr('size', Math.max($input.val().length, 10));
// 2. resizing the model to the size of the input + padding.
model.resize($input.outerWidth() + 5, $input.outerHeight() + 40);
}
$('input').on('input', onTextInput);
JS Fiddle: http://jsfiddle.net/kumilingus/Lrffgvqn/
Similar with HTML TextArea, where the only difference will be the way how you auto-scale it based on the text inside.

JQuery Flot doesn't draw bar chart + how to display values from data in tooltip?

I have a problem with JQuery Flot chart. It doesn't show the bar chart (data_campaigns) at all but the (data_campaigns2) shows up just fine.
I would also like to know how to show data from both charts in tooltip. Now the tooltip is just showing random X and Y variables but I would like it show the amount of clicks.
//Chart - Campaigns
$(function () {
var data_campaigns = [
[1359766800,8],[1359853200,4],[1359939600,11],[1360026000,11],
[1360112400,15],[1360198800,12],[1360285200,16],[1360371600,7],
[1360458000,9],[1360544400,6],[1360630800,13],[1360717200,12],
[1360803600,6],[1360890000,13],[1360976400,3],[1361062800,9],
[1361149200,18],[1361235600,18],[1361322000,12],[1361408400,14],
[1361494800,7],[1361581200,5],[1361667600,3],[1361754000,9],
[1361840400,15],[1361926800,14],[1362013200,4],[1362099600,0],
[1362186000,0],[1362272400,0]];
var data_campaigns2 = [
[1359766800,8],[1359853200,4],[1359939600,11],[1360026000,11],
[1360112400,15],[1360198800,12],[1360285200,16],[1360371600,7],
[1360458000,9],[1360544400,6],[1360630800,13],[1360717200,12],
[1360803600,6],[1360890000,13],[1360976400,3],[1361062800,9],
[1361149200,18],[1361235600,18],[1361322000,12],[1361408400,14],
[1361494800,7],[1361581200,5],[1361667600,3],[1361754000,9],
[1361840400,15],[1361926800,14],[1362013200,4],[1362099600,0],
[1362186000,0],[1362272400,0]];
var plot = $.plot($("#placeholder"),
[ { data: data_campaigns,color:"rgba(0,0,0,0.2)", shadowSize:0,
bars: {
show: true,
lineWidth: 0,
fill: true,
fillColor: { colors: [ { opacity: 1 }, { opacity: 1 } ] }
}
} ,
{ data: data_campaigns2,
color:"rgba(255,255,255, 0.4)",
shadowSize:0,
lines: {show:true, fill:false}, points: {show:false},
bars: {show:false},
}
],
{
series: {
bars: {show:true, barWidth: 0.6}
},
grid: { show:false, hoverable: true, clickable: false, autoHighlight: true, borderWidth:0 },
yaxis: {
min: 0
},
xaxis: {
tickDecimals: 0
}
});
function showTooltip(x, y, contents) {
console.log(x+","+y);
var d = new Date(contents *1000);
var curr_date = d.getDate();
var curr_month = d.getMonth();
curr_month++;
var curr_year = d.getFullYear();
$('<div id="tooltip"><div class="date">'+curr_date + "." + curr_month + "." + curr_year+'<\/div><div class="title text_color_3">'+x+'%<\/div> <div class="description text_color_3">CTR<\/div><div class="title ">'+y+'<\/div><div class="description">Clicks<\/div><\/div>').css( {
position: 'absolute',
display: 'none',
top: y - 125,
left: x - 40,
border: '0px solid #ccc',
padding: '2px 6px',
'background-color': '#fff',
opacity: 10
}).appendTo("body").fadeIn(200);
}
var previousPoint = null;
$("#placeholder").bind("plothover", function (event, pos, item) {
$("#x").text(pos.x.toFixed(2));
$("#y").text(pos.y.toFixed(2));
if (item) {
if (previousPoint != item.dataIndex) {
previousPoint = item.dataIndex;
$("#tooltip").remove();
var x = item.datapoint[0].toFixed(2),
y = item.datapoint[1].toFixed(2);
showTooltip(item.pageX, item.pageY,
x);
}
}
});
1) the x, y parameters in function showTooltip(x, y, contents)
are actually not x,y values from your chart, bude x, y coordinates where to place the tooltip at. The tooltip value (text displayed in tooltip) is in parameter contents, so instead of:
$('<div id="tooltip"><div class="date">'+curr_date + "." + curr_month + "." + curr_year+'<\/div><div class="title text_color_3">'+x+'%<\/div> <div class="description text_color_3">CTR<\/div><div class="title ">'+y+'<\/div><div class="description">Clicks<\/div><\/div>').css( {...
you need something like this:
$('<div id="tooltip">' + contents + '<\/div>').css({...
with contents variable filled with whatever you need.
2) you need to set mode option
xaxis: {
mode: 'time',
...
}
and play a bit with the options to display bars. in the jsfiddle example below i set the lineWidth: 10 and changed some colors
3) Blake's advice about the timestamps is right. (not solving the bar visibility, but solving the correct x axis date values), when populating the data array, multiply them by 1000 to be displayed correctly
here is the jsFiddle, have a look at it

Resources