JointJS baseline-shift - jointjs

I'm using JointJS 3.2.0, and I need to have texts like PN=xxx.
How baseline-shift is supposed to be used in JointJS? I tried
markup: '<g><text>\
<tspan class="left"></tspan>\
<tspan class="sub"></tspan>\
<tspan class="right"></tspan>\
</text></g>',
attrs: {
'.sub': {
'baseline-shift': 'sub',
},
but it creates another tspan inside each tspan which all have dy=0.
Similar approach with didn't make the correct tspan have the baseline-shift attribute.
I tried another approach where I changed textVerticalAnchor to 'top', but in that case I can't control the spacing between each tspan, and the letters are spaced further apart than just inside one tspan.

Using Text Annotations might help here.
element.attr('label', {
text: 'Pn = xxx',
annotations: [{ start: 1, end: 2, attrs: { 'baseline-shift': 'sub' }}]
});
Here's a JSFiddle.

Related

access svg element in a arearange HighCharts chart

The last line of the following code (brutally copied from a HighCharts demo chart page) changes the opacity of a arearange series:
var chart=Highcharts.chart('container', {
series: [{
name: 'Temperature',
data: averages,
zIndex: 1,
marker: {
fillColor: 'white',
lineWidth: 2,
lineColor: Highcharts.getOptions().colors[0]
}
}, {
name: 'Range',
data: ranges,
type: 'arearange',
lineWidth: 0,
linkedTo: ':previous',
color: Highcharts.getOptions().colors[0],
fillOpacity: 0.3,
zIndex: 0,
marker: {
enabled: false
}
}]
});
chart.series[1].update({fillOpacity:0.7},true);
JSFiddle here.
(the opacity could be set directly inside the chart object, but I need to modify it after the chart creation, the code is a toy example to simulate my needs)
My real chart has several arearange series, and updating them is heavy. Is there a way to modify such property avoiding the .update method? that it, is there a way to directly access the svg element corresponding to the arearange series?
I've tried to check the series .graph property, as previously suggested in an answer to a similar question, but in this case arearange series has no .graph property.
It can be done exactly the same as in the answer mentioned in your question but instead of modifying the graph you have to modify the area property.
Code:
chart.series[1].area.attr({
fill: 'rgba(124,181,236,0.7)'
});
Demo:
https://jsfiddle.net/BlackLabel/abohcq4x/

How to add an image to an element as a decorator?

Imagine I have Rect element and I wish to decorate it with a small (say 16x16) PNG image in the upper left. I am unable to determine how to achieve that task. I have studied the docs but have (so far) been unable to find a sample or reference on how to achieve that task. Does anyone have a recipe or a sample pointer that they would be willing to share to help me achieve my goal?
Better is to create your own custom shape that has a rectangle, image and text. This gives you much more flexibility and you don't have to have two elements in order to express one shape. Your shape decorated with a little image in the top left corner may look like:
joint.shapes.basic.DecoratedRect = joint.shapes.basic.Generic.extend({
markup: '<g class="rotatable"><g class="scalable"><rect/></g><image/><text/></g>',
defaults: joint.util.deepSupplement({
type: 'basic.DecoratedRect',
size: { width: 100, height: 60 },
attrs: {
'rect': { fill: '#FFFFFF', stroke: 'black', width: 100, height: 60 },
'text': { 'font-size': 14, text: '', 'ref-x': .5, 'ref-y': .5, ref: 'rect', 'y-alignment': 'middle', 'x-alignment': 'middle', fill: 'black' },
'image': { 'ref-x': 2, 'ref-y': 2, ref: 'rect', width: 16, height: 16 }
}
}, joint.shapes.basic.Generic.prototype.defaults)
});
And you can use it like this in your diagrams:
var decoratedRect = new joint.shapes.basic.DecoratedRect({
position: { x: 150, y: 80 },
size: { width: 100, height: 60 },
attrs: {
text: { text: 'My Element' },
image: { 'xlink:href': 'http://placehold.it/16x16' }
}
});
graph.addCell(decoratedRect);
Note how is the shape specified, the important bits are the markup, type and the attrs object that references the SVG elements in the markup by normal CSS selectors (here just tag selectors but you can use classes if you want). For the image tag, we take advantage of the JointJS special attributes for relative positioning (ref, ref-x and ref-y). With these attributes, we position the image relatively to the top left corner of the rect element and we offset it by 2px from the top edge (ref-y) and 2px from the left edge (ref-x).
One note: It is important that the type attribute ('basic.DecoratedRect') matches the namespace the shape is defined in (joint.shapes.basic.DecoratedRect). This is because when JointJS re-constructs graphs from JSON, it looks at the type attribute and makes a simple lookup to the joint.shapes namespace to see if there is a shape defined for this type.
We can create an element type for an image using the following recipe:
var image = new joint.shapes.basic.Image({
position : {
x : 100,
y : 100
},
size : {
width : 16,
height : 16
},
attrs : {
image : {
"xlink:href" : "images/myImage.png",
width : 16,
height : 16
}
}
});
graph.addCell(image);
This will position the image at x=100,y=100. It is important to make the size width/height match the attrs/image width/height and be the width/height of the image itself.
Although this doesn't decorate a previous element, it can be positioned over a previous element achieving the desired effect.

How to create SVG shapes based on data?

I have structure with data and shapes definition:
var data = [
{
"id": "first",
"shapes": [
{
"shape": "polygon",
"points": [["8","64"],["8","356"],["98","356"],["98","64"]]
}
]
},
{
"id": "second",
"shapes": [
{
"shape": "ellipse",
"cx": "63", "cy": "306", "rx": "27","ry": "18"
}, {
"shape": "polygon",
"points": [["174","262"],["171","252"],["167","262"]]
}
]
}
]; // in the data may be stored any SVG shape
I would like to create SVG:
<svg width="218" height="400">
<g transform="translate(0,400) scale(1,-1)">
<g>
<polygon points="8,64 8,356 98,356 98,64"/>
</g>
<g>
<ellipse cx="63" cy="306" rx="27" ry="18"/>
<polygon points="174,262 171,252 167,262"/>
</g>
</g>
</svg>
For each data element I'm appending <g>:
var groups = svg.selectAll("g").data(data, function (d) {
return d.id;
});
groups.enter().append("g");
Now I'm binding data for group shapes:
var shapes = groups.selectAll(".shape").data(function (d) {
return d.shapes;
}, function(d,i){return [d.shape,i].join('-');});
So far it was as expected. Now I want to for each entering DOM node dispatch drawing function with proper shape but apparently shapes.enter().each() is not working in this context (not defined). I suppose it works rather on each DOM node than on each data to be bound. And this is working:
shapes.enter().append("g").each(function(draw, i) {
var shape = draw.shape;
d3.select(this).call(drawer[shape]);
});
But painful side-effect is that SVG has two levels of <g>:
<svg width="218" height="400">
<g transform="translate(0,400) scale(1,-1)">
<g>
<g>
<polygon points="8,64 8,356 98,356 98,64"/>
</g>
</g>
...
</svg>
How to get rid of that? How to build data based shapes correctly?
Two ways to add basic shapes based on data provided by AmeliaBR are ok but slightly outside d3.js API. After long consideration I've decided to add answer my own question.
Inspiration for searching other solution was comment of Lars Kotthoff under question suggesting using paths instead of primitive SVG shapes. In this approach instead of adding second level of <g> there should be added <path>:
shapes.enter().append("path").attr("d", function(d) {
return drawer[d.shape](d);
});
Original drawer object generating basic shapes would return value for attribute d of <path>.
var drawer = {
polygon: function (d) {
return [
"M", d.points[0].join(','),
"L", d.points
.slice(1, d.points.length)
.map(function (e) {
return e.join(",")
}), "Z"].join(" ");
},
ellipse: function (d) {
return [
'M', [d.cx, d.cy].join(','),
'm', [-d.rx, 0].join(','),
'a', [d.rx, d.ry].join(','),
0, "1,0", [2 * d.rx, 0].join(','),
'a', [d.rx, d.ry].join(','),
0, "1,0", [-2 * d.rx, 0].join(',')].join(' ');
},
circle: function (d) {
return this.ellipse({
cx: d.cx,
cy: d.cy,
rx: d.r,
ry: d.r
});
},
rect: function (d) {
return this.polygon({
points: [
[d.x, d.y],
[d.x + d.width, d.y],
[d.x + d.width, d.y + d.height],
[d.x, d.y + d.height]
]
});
},
path: function (d) {
return d.d;
}
};
Working example you can check at http://fiddle.jshell.net/daKkJ/4/
Tricky question. Shame that you can't call each on an enter() selection. Neither can you use a function(d){} in an append statement.(See comments)
But I got it working, using a Javascript forEach() call on the data array itself. It calls your function with the array entry, index, and array itself as parameters, and you can specify a this context -- I just passed in the desired parent element as a selection.
The fabulous fiddle: http://fiddle.jshell.net/994XM/1/
(simpler than your case, but should be able to adapt easily)
It's a bit confusing since the D3 code inside the forEach is all per element, with the d and i values already available for you, so you don't need any internal functions in your D3 method calls. But once you figure that out, it all works.
I found this question through Google, and the jsfiddles linked to above have ceased to work. You now have to call document.createElementNS, giving it the SVG namespace, otherwise the shapes don't show up.
I created an updated fiddle. I also cleaned out a lot of the unnecessary bits, so it should be easier to see what's going on.

SVG text scales inproperly

I use Ext.daw.* to draw svg text. The root element has size 200x300.
If some element has larger size than size of root element then everything scales properly except the text: text appears to have larger size.
Check out this demo. How to make text scale properly?
Ext.create('Ext.draw.Component', {
renderTo: Ext.getBody(),
width: 200,
height: 300,
items: [{
type: 'path',
path: 'M0 0 V200',
'stroke-width': 3,
stroke: 'green'
},{
type: 'path',
// if I set path to 'M200 0 V700' then text goes crazy
path: 'M200 0 V200',
'stroke-width': 3,
stroke: 'green'
},{
type: 'text',
x: 0,
y: 50,
// text is located accurately between two lines
// but when one of the lines exceeds size of the canvas
// text's size changes
text: 'wwwwwwwwwwwwwwwwwww',
font: "18px monospace"
}]
});
Text is subject to hinting and kerning that happen differently at different point sizes and so does not normally scale uniformly. There is a hint available to indicate you would like this overridden:
text-rendering="geometricPrecision"
Changing your code to
},{
type: 'text',
x: 0,
y: 50,
text: 'wwwwwwwwwwwwwwwwwww',
'text-rendering': 'geometricPrecision',
font: "17px monospace"
}]
Should make it work more like you want it too, although it will display less clearly at small point sizes.

Extjs tabpanel with big complex objects or dynamically loaded objects

I have some problem with complex objects on tabpanel.
I have 2 complex objects with stores, windows, grids, trees and etc. Here is the beginning of the object:
Ext.define('Ext.app.DocumentsContainer', {
extend: 'Ext.container.Container',
initComponent: function(){
var documentsStore = Ext.create('Ext.data.Store', {
And I have tabpanel with 2 panels(one for each object).
full code of viewport with tabpanel:
Ext.create('Ext.container.Viewport', {
layout: 'border',
padding: '5',
items: [
{
xtype: 'container',
region: 'north',
height: 50
},
{
xtype: 'tabpanel',
activeTab: 0,
region: 'center',
width: 100,
items: [
{
xtype: 'panel',
title: 'Documents',
layout: 'border',
items: Ext.create('Ext.app.DocumentsContainer'),
},{
xtype: 'panel',
title: 'Transmittals',
layout: 'border',
items: [Ext.create('Ext.app.TransmittalsContainer')],
}]
}
],
});
When I testing my page, a have a problem, because somehow data from one object dysplays in grid in another object, or doesnt dysplay at all.
But both objects working correctly one at a time.
I think, I can fix it by dynamically loading objects when tab is opened, but dont know how can I do it.
Any suggestions?
Without knowing the internals of your two components Ext.app.DocumentsContainer and Ext.app.TransmittalsContainer one cannot reliably answer your question. Perhaps you assign the same ID to two different internal components - that's most often the reason for mixed up data.
Secondly, Frederic is right - you're overnesting your components. Both panels inside the tabpanel are unnecessary because you can put your components directly into the tabpanel as illustrated by Frederic. However, if you insist to keep your panels, try changing the layout to fit because that layout-type handles single-item-components (sized the single component to fit into the parent frame).
You are overnesting the tabpanel. The tabpanels items will automatically stretch their content. So it might just work for you.
Do this instead. Change DocumentContainer to pe panel (so it can have a title. )
Ext.define('Ext.app.DocumentsContainer', {
extend: 'Ext.panel.Panel',
initComponent: function(){
var documentsStore = Ext.create('Ext.data.Store', {
And change you tabpanel to this. I also deleted the width on your center region. center regions fills out the rest of the border layouts space automatically.
Ext.create('Ext.container.Viewport', {
layout: 'border',
padding: '5',
items: [
{
xtype: 'container',
region: 'north',
height: 50
},
{
xtype: 'tabpanel',
activeTab: 0,
region: 'center',
items: [
Ext.create('Ext.app.DocumentsContainer', {title:'Documents'}),
Ext.create('Ext.app.TransmittalsContainer', {title:'Transmittals'})
]
}
],
});
Also. as Stefan mentioned. Never ever use id´s in your ExtJS4 code. They are evil and will freck your result up eventually. itemId or just simple Ext.DomQuery is the way to do it.

Resources