Skip to content

Commit

Permalink
[jsroot] dev 2/10/2024 with #url[link]{label} support
Browse files Browse the repository at this point in the history
1. Let add external links via `#url[link]{label}` syntax
2. Support URLs when export into PDF
3. Support TAttMarker style with line width bigger than 1
  • Loading branch information
linev committed Oct 2, 2024
1 parent e078ba6 commit 3bee1df
Show file tree
Hide file tree
Showing 13 changed files with 323 additions and 121 deletions.
210 changes: 154 additions & 56 deletions js/build/jsroot.js

Large diffs are not rendered by default.

23 changes: 15 additions & 8 deletions js/changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,21 @@
25. Upgrade three.js r162 -> r168, use r162 only in node.js because of "gl" module
26. Create unified svg2pdf/jspdf ES6 modules, integrate in jsroot builds
27. Let create multipage PDF document - in TWebCanvas batch mode
28. Internals - upgrade to eslint 9
29. Internals - do not select pad (aka gPad) for objects drawing, always use assigned pad painter
30. Fix - properly save zoomed ranges in drawingJSON()
31. Fix - properly redraw TMultiGraph
32. Fix - show empty bin in TProfile2D if it has entries #316
33. Fix - saving embed TGeo in TCanvas into image
34. Fix - unzooming on log scale was extending range forevever
35. Fix - correct `#font[id]` handling in latex
28. Let add external links via `#url[link]{label}` syntax - including jsPDF support
29. Support TAttMarker style with line width bigger than 1
30. Internals - upgrade to eslint 9
31. Internals - do not select pad (aka gPad) for objects drawing, always use assigned pad painter
32. Fix - properly save zoomed ranges in drawingJSON()
33. Fix - properly redraw TMultiGraph
34. Fix - show empty bin in TProfile2D if it has entries #316
35. Fix - unzooming on log scale was extending range forevever


## Changes in 7.7.4
1. Fix - TGraph Y range selection, do not cross 0
2. Fix - correctly handle `#font[id]` in latex
3. Fix - store canvas with embed geometry drawing
4. Fix - upgrade rollup and import.meta polyfill


## Changes in 7.7.3
Expand Down
11 changes: 5 additions & 6 deletions js/modules/base/FontHandler.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { isNodeJs, httpRequest, btoa_func, source_dir, settings, isObject } from


const kArial = 'Arial', kTimes = 'Times New Roman', kCourier = 'Courier New', kVerdana = 'Verdana', kSymbol = 'RootSymbol', kWingdings = 'Wingdings',
// average width taken from symbols.html, counted only for letters and digits
root_fonts = [null, // index 0 not exists
// average width taken from symbols.html, counted only for letters and digits
root_fonts = [null, // index 0 not exists
{ n: kTimes, s: 'italic', aw: 0.5314 },
{ n: kTimes, w: 'bold', aw: 0.5809 },
{ n: kTimes, s: 'italic', w: 'bold', aw: 0.5540 },
Expand All @@ -22,10 +22,9 @@ root_fonts = [null, // index 0 not exists
{ n: kVerdana, aw: 0.5664 },
{ n: kVerdana, s: 'italic', aw: 0.5495 },
{ n: kVerdana, w: 'bold', aw: 0.5748 },
{ n: kVerdana, s: 'italic', w: 'bold', aw: 0.5578 }];


const gFontFiles = {};
{ n: kVerdana, s: 'italic', w: 'bold', aw: 0.5578 }],
// list of loaded fonts including handling of multiple simultaneous requests
gFontFiles = {};

/** @summary Read font file from some pre-configured locations
* @return {Promise} with base64 code of the font
Expand Down
4 changes: 2 additions & 2 deletions js/modules/base/ObjectPainter.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ class ObjectPainter extends BasePainter {
* or svg:g element created in specified frame layer ('main_layer' will be used when true specified)
* @param {boolean|string} [frame_layer] - when specified, <g> element will be created inside frame layer, otherwise in the pad
* @protected */
createG(frame_layer) {
createG(frame_layer, use_a = false) {
let layer;

if (frame_layer === 'frame2d') {
Expand Down Expand Up @@ -336,7 +336,7 @@ class ObjectPainter extends BasePainter {
// clear all elements, keep g element on its place
this.draw_g.selectAll('*').remove();
} else {
this.draw_g = layer.append('svg:g');
this.draw_g = layer.append(use_a ? 'svg:a' : 'svg:g');

if (!frame_layer)
layer.selectChildren('.most_upper_primitives').raise();
Expand Down
26 changes: 19 additions & 7 deletions js/modules/base/TAttMarkerHandler.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import { select as d3_select } from '../d3.mjs';
import { getColor } from './colors.mjs';


const root_markers = [
// list of marker types which can have line widths
const root_50_67 = [2, 3, 5, 4, 25, 26, 27, 28, 30, 32, 35, 36, 37, 38, 40, 42, 44, 46],
// internal recoding of root markers
root_markers = [
0, 1, 2, 3, 4, // 0..4
5, 106, 107, 104, 1, // 5..9
1, 1, 1, 1, 1, // 10..14
Expand Down Expand Up @@ -135,8 +138,15 @@ class TAttMarkerHandler {
}

this.optimized = false;
this.lwidth = 1;

const marker_kind = root_markers[this.style] ?? 104,
let style = this.style;
if (style >= 50) {
this.lwidth = 2 + Math.floor((style - 50) / root_50_67.length);
style = root_50_67[(style - 50) % root_50_67.length];
}

const marker_kind = root_markers[style] ?? 104,
shape = marker_kind % 100;

this.fill = (marker_kind >= 100);
Expand All @@ -152,7 +162,8 @@ class TAttMarkerHandler {
s3 = (size/3).toFixed(this.ndig),
s4 = (size/4).toFixed(this.ndig),
s8 = (size/8).toFixed(this.ndig),
s38 = (size*3/8).toFixed(this.ndig);
s38 = (size*3/8).toFixed(this.ndig),
s34 = (size*3/4).toFixed(this.ndig);

switch (shape) {
case 1: // dot
Expand All @@ -163,17 +174,17 @@ class TAttMarkerHandler {
this.marker = `v${s1}m-${s2},-${s2}h${s1}`;
break;
case 3: // asterisk
this.x0 = this.y0 = -size / 2;
this.marker = `l${s1},${s1}m0,-${s1}l-${s1},${s1}m0,-${s2}h${s1}m-${s2},-${s2}v${s1}`;
this.y0 = -size / 2;
this.marker = `v${s1}m-${s2},-${s2}h${s1}m-${s8},-${s38}l-${s34},${s34}m${s34},0l-${s34},-${s34}`;
break;
case 4: // circle
this.x0 = -parseFloat(s2);
s1 = (parseFloat(s2) * 2).toFixed(this.ndig);
this.marker = `a${s2},${s2},0,1,0,${s1},0a${s2},${s2},0,1,0,-${s1},0z`;
break;
case 5: // multiply
this.x0 = this.y0 = -size / 2;
this.marker = `l${s1},${s1}m0,-${s1}l-${s1},${s1}`;
this.x0 = this.y0 = -3 / 8 * size;
this.marker = `l${s34},${s34}m0,-${s34}l-${s34},${s34}`;
break;
case 6: // small dot
this.x0 = -1;
Expand Down Expand Up @@ -271,6 +282,7 @@ class TAttMarkerHandler {
apply(selection) {
this.used = true;
selection.style('stroke', this.stroke ? this.color : 'none')
.style('stroke-width', this.stroke && (this.lwidth > 1) ? this.lwidth : null)
.style('fill', this.fill ? this.color : 'none');
}

Expand Down
25 changes: 21 additions & 4 deletions js/modules/base/latex.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ const latex_features = [
{ name: '#scale[', arg: 'float' }, // font scale
{ name: '#color[', arg: 'int' }, // font color
{ name: '#font[', arg: 'int' }, // font face
{ name: '#url[', arg: 'string' }, // url link
{ name: '_{', low_up: 'low' }, // subscript
{ name: '^{', low_up: 'up' }, // superscript
{ name: '#bar{', deco: 'overline' /* accent: '\u02C9' */ }, // '\u0305'
Expand Down Expand Up @@ -394,7 +395,7 @@ function producePlainText(painter, txt_node, arg) {
if (arg.font?.isSymbol) {
txt_node.text(replaceSymbols(arg.text, arg.font.isSymbol));
txt_node.property('$text', arg.text);
txt_node.property('$font', arg.font)
txt_node.property('$font', arg.font);
} else
txt_node.text(arg.text);
}
Expand Down Expand Up @@ -457,14 +458,14 @@ function parseLatex(node, arg, label, curr) {
},

/** Create special sub-container for elements like sqrt or braces */
createGG = () => {
createGG = (is_a) => {
const gg = currG();

// this is indicator that gg element will be the only one, one can use directly main container
if ((nelements === 1) && !label && !curr.x && !curr.y)
return gg;

return makeTranslate(gg.append('svg:g'), curr.x, curr.y);
return makeTranslate(gg.append(is_a ? 'svg:a' : 'svg:g'), curr.x, curr.y);
},

extractSubLabel = (check_first, lbrace, rbrace) => {
Expand Down Expand Up @@ -583,7 +584,7 @@ function parseLatex(node, arg, label, curr) {
if (curr.font?.isSymbol) {
elem.text(replaceSymbols(s, curr.font.isSymbol));
elem.property('$text', s);
elem.property('$font', curr.font)
elem.property('$font', curr.font);
} else
elem.text(s);

Expand Down Expand Up @@ -916,6 +917,22 @@ function parseLatex(node, arg, label, curr) {
continue;
}

if (found.name === '#url[') {
const sublabel = extractSubLabel();
if (sublabel === -1) return false;

const gg = createGG(true),
subpos = createSubPos();

gg.attr('href', foundarg);

parseLatex(gg, arg, sublabel, subpos);

positionGNode(subpos, 0, 0, true);
shiftX(subpos.rect.width);
continue;
}

if ((found.name === '#color[') || (found.name === '#scale[') || (found.name === '#font[')) {
const sublabel = extractSubLabel();
if (sublabel === -1) return false;
Expand Down
64 changes: 53 additions & 11 deletions js/modules/base/svg2pdf.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1196,19 +1196,27 @@ function getBoundingBoxByChildren(context, svgnode) {
if (getAttribute(svgnode.element, context.styleSheets, 'display') === 'none') {
return [0, 0, 0, 0];
}
var boundingBox = [0, 0, 0, 0];
var boundingBox = [];
svgnode.children.forEach(function (child) {
var nodeBox = child.getBoundingBox(context);
boundingBox = [
Math.min(boundingBox[0], nodeBox[0]),
Math.min(boundingBox[1], nodeBox[1]),
Math.max(boundingBox[0] + boundingBox[2], nodeBox[0] + nodeBox[2]) -
if ((nodeBox[0] === 0) && (nodeBox[1] === 0) && (nodeBox[2] === 0) && (nodeBox[3] === 0))
return;
var transform = child.computeNodeTransform(context);
nodeBox[0] = nodeBox[0] * transform.sx + transform.tx;
nodeBox[1] = nodeBox[1] * transform.sy + transform.ty;
if (boundingBox.length === 0)
boundingBox = nodeBox;
else
boundingBox = [
Math.min(boundingBox[0], nodeBox[0]),
Math.max(boundingBox[1] + boundingBox[3], nodeBox[1] + nodeBox[3]) -
Math.min(boundingBox[1], nodeBox[1])
];
Math.min(boundingBox[1], nodeBox[1]),
Math.max(boundingBox[0] + boundingBox[2], nodeBox[0] + nodeBox[2]) -
Math.min(boundingBox[0], nodeBox[0]),
Math.max(boundingBox[1] + boundingBox[3], nodeBox[1] + nodeBox[3]) -
Math.min(boundingBox[1], nodeBox[1])
];
});
return boundingBox;
return boundingBox.length === 0 ? [0, 0, 0, 0] : boundingBox;
}
function defaultBoundingBox(element, context) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down Expand Up @@ -2933,7 +2941,9 @@ var TextChunk = /** @class */ (function () {
var TextNode = /** @class */ (function (_super) {
__extends(TextNode, _super);
function TextNode() {
return _super !== null && _super.apply(this, arguments) || this;
var _this = _super !== null && _super.apply(this, arguments) || this;
_this.boundingBox = [];
return _this;
}
TextNode.prototype.processTSpans = function (textNode, node, context, textChunks, currentTextSegment, trimInfo) {
var pdfFontSize = context.pdf.getFontSize();
Expand Down Expand Up @@ -3029,6 +3039,9 @@ var TextNode = /** @class */ (function (_super) {
renderingMode: textRenderingMode === 'fill' ? void 0 : textRenderingMode,
charSpace: charSpace === 0 ? void 0 : charSpace
});
this.boundingBox = [textX + dx - xOffset, textY + dy - pdfFontSize, context.textMeasure.measureTextWidth(transformedText, context.attributeState), pdfFontSize];
if (alignmentBaseline === 'baseline')
this.boundingBox[1] += pdfFontSize * 0.2;
}
}
else {
Expand Down Expand Up @@ -3080,7 +3093,7 @@ var TextNode = /** @class */ (function (_super) {
return svgNodeAndChildrenVisible(this, parentVisible, context);
};
TextNode.prototype.getBoundingBoxCore = function (context) {
return defaultBoundingBox(this.element, context);
return this.boundingBox.length > 0 ? this.boundingBox : defaultBoundingBox(this.element, context);
};
TextNode.prototype.computeNodeTransformCore = function (context) {
return context.pdf.unitMatrix;
Expand Down Expand Up @@ -5202,6 +5215,33 @@ var Group = /** @class */ (function (_super) {
};
return Group;
}(ContainerNode));
var GroupA = /** @class */ (function (_super) {
__extends(GroupA, _super);
function GroupA() {
return _super !== null && _super.apply(this, arguments) || this;
}
GroupA.prototype.renderCore = function (context) {
return __awaiter(this, void 0, void 0, function () {
var href, box, scale, ph;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, _super.prototype.renderCore.call(this, context)];
case 1:
_a.sent();
href = getAttribute(this.element, context.styleSheets, 'href');
if (href) {
box = this.getBoundingBox(context);
scale = context.pdf.internal.scaleFactor;
ph = context.pdf.internal.pageSize.getHeight();
context.pdf.link(scale * (box[0] * context.transform.sx + context.transform.tx), ph - scale * (box[1] * context.transform.sy + context.transform.ty), scale * box[2], scale * box[3], { url: href });
}
return [2 /*return*/];
}
});
});
};
return GroupA;
}(Group));

var ClipPath = /** @class */ (function (_super) {
__extends(ClipPath, _super);
Expand Down Expand Up @@ -5263,6 +5303,8 @@ function parse$1(node, idMap) {
forEachChild(node, function (i, n) { return children.push(parse$1(n, idMap)); });
switch (node.tagName.toLowerCase()) {
case 'a':
svgnode = new GroupA(node, children);
break;
case 'g':
svgnode = new Group(node, children);
break;
Expand Down
2 changes: 1 addition & 1 deletion js/modules/core.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const version_id = 'dev',

/** @summary version date
* @desc Release date in format day/month/year like '14/04/2022' */
version_date = '27/09/2024',
version_date = '2/10/2024',

/** @summary version id and date
* @desc Produced by concatenation of {@link version_id} and {@link version_date}
Expand Down
10 changes: 7 additions & 3 deletions js/modules/draw/more.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ async function drawText() {
pp = this.getPadPainter(),
w = pp.getPadWidth(),
h = pp.getPadHeight(),
fp = this.getFramePainter();
fp = this.getFramePainter(),
is_url = text.fName.startsWith('http://') || text.fName.startsWith('https://');
let pos_x = text.fX, pos_y = text.fY, use_frame = false,
fact = 1,
annot = this.matchObjectType(clTAnnotation);
Expand All @@ -38,7 +39,7 @@ async function drawText() {
text.fTextAlign = 22;
}

this.createG(use_frame ? 'frame2d' : undefined);
this.createG(use_frame ? 'frame2d' : undefined, is_url);

this.draw_g.attr('transform', null); // remove transform from interactive changes

Expand All @@ -59,6 +60,9 @@ async function drawText() {
fact = 0.8;
}

if (is_url)
this.draw_g.attr('href', text.fName).append('title').text(`Link on ${text.fName}`);

return this.startTextDrawingAsync(this.textatt.font, this.textatt.getSize(w, h, fact, 0.05))
.then(() => this.drawText(arg))
.then(() => this.finishTextDrawing())
Expand Down Expand Up @@ -91,7 +95,7 @@ async function drawText() {
}

if (annot !== '3d')
addMoveHandler(this);
addMoveHandler(this, true, is_url);
else {
fp.processRender3D = true;
this.handleRender3D = () => {
Expand Down
3 changes: 2 additions & 1 deletion js/modules/gpad/TAxisPainter.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -555,7 +555,8 @@ class TAxisPainter extends ObjectPainter {
this.nticks2 = 1;
}
this.noexp = axis?.TestBit(EAxisBits.kNoExponent);
if ((this.scale_max < 300) && (this.scale_min > 0.3) && !this.noexp_changed) this.noexp = true;
if ((this.scale_max < 300) && (this.scale_min > 0.3) && !this.noexp_changed && (this.log === 1))
this.noexp = true;
this.moreloglabels = axis?.TestBit(EAxisBits.kMoreLogLabels);
this.format = this.formatLog;
} else if (this.kind === kAxisLabels) {
Expand Down
Loading

0 comments on commit 3bee1df

Please sign in to comment.