Skip to content

Commit c0ead0d

Browse files
chihuahuajart
authored andcommitted
Implement getPathSegAtLength in JavaScript (#434)
In SVG 2, getPathSegAtLength (which the graph explorer depends on as part of rendering edges), is deprecated. This means that the graph explorer would render with no edges in Chrome 62 (current in the dev channel) and would thus be very much broken in Chrome 62. This commit pre-empts that change by implementing the functionality of getPathSegAtLength in the graph explorer so that the explorer still works in Chrome 62. I do not see alternative ways of implementing this functionality using the native pathLength property + getTotalLength() and getPointAtLength() methods still provided by the SVG API. After this change goes in, we should quickly release a new pypi package and internal version of TensorBoard so that the graph explorer is not broken for users of Chrome 62. Fixes #425.
1 parent 9b0ee03 commit c0ead0d

File tree

2 files changed

+39
-11
lines changed

2 files changed

+39
-11
lines changed

tensorboard/plugins/graph/tf_graph_common/edge.ts

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,34 @@ export function getLabelForEdge(metaedge: Metaedge,
152152
getLabelForBaseEdge(metaedge.baseEdgeList[0], renderInfo);
153153
}
154154

155+
/**
156+
* Computes the index into a set of points that constitute a path for which the
157+
* distance along the path from the initial point is as large as possible
158+
* without exceeding the length. This function was introduced after the
159+
* native getPathSegAtLength method got deprecated by SVG 2.
160+
* @param points Array of path control points. A point has x and y properties.
161+
* Must be of length at least 2.
162+
* @param length The length (float).
163+
* @param lineFunc A function that takes points and returns the "d" attribute
164+
* of a path made from connecting the points.
165+
* @return The index into the points array.
166+
*/
167+
function getPathSegmentIndexAtLength(
168+
points: render.Point[],
169+
length: number,
170+
lineFunc: (points: render.Point[]) => string): number {
171+
const path = document.createElementNS(tf.graph.scene.SVG_NAMESPACE, 'path');
172+
for (let i = 1; i < points.length; i++) {
173+
path.setAttribute("d", lineFunc(points.slice(0, i)));
174+
if (path.getTotalLength() > length) {
175+
// This many points has already exceeded the length.
176+
return i - 1;
177+
}
178+
}
179+
// The entire path is shorter than the specified length.
180+
return points.length - 1;
181+
}
182+
155183
/**
156184
* Shortens the path enought such that the tip of the start/end marker will
157185
* point to the start/end of the path. The marker can be of arbitrary size.
@@ -183,7 +211,7 @@ function adjustPathPointsForMarker(points: render.Point[],
183211
const point = pathNode.getPointAtLength(length);
184212
// Figure out how many segments of the path we need to remove in order
185213
// to shorten the path.
186-
const segIndex = pathNode.getPathSegAtLength(length);
214+
const segIndex = getPathSegmentIndexAtLength(points, length, lineFunc);
187215
// Update the very first segment.
188216
points[segIndex - 1] = {x: point.x, y: point.y};
189217
// Ignore every point before segIndex - 1.
@@ -197,7 +225,7 @@ function adjustPathPointsForMarker(points: render.Point[],
197225
const point = pathNode.getPointAtLength(length);
198226
// Figure out how many segments of the path we need to remove in order
199227
// to shorten the path.
200-
const segIndex = pathNode.getPathSegAtLength(length);
228+
const segIndex = getPathSegmentIndexAtLength(points, length, lineFunc);
201229
// Update the very last segment.
202230
points[segIndex] = {x: point.x, y: point.y};
203231
// Ignore every point after segIndex.

tensorboard/plugins/graph/tf_graph_common/scene.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ See the License for the specific language governing permissions and
1313
limitations under the License.
1414
==============================================================================*/
1515
module tf.graph.scene {
16-
const svgNamespace = 'http://www.w3.org/2000/svg';
16+
export const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
1717

1818
/** Enums element class of objects in the scene */
1919
export let Class = {
@@ -610,14 +610,14 @@ function _addHealthPill(
610610
healthPillHeight /= 2;
611611
}
612612

613-
let healthPillGroup = document.createElementNS(svgNamespace, 'g');
613+
let healthPillGroup = document.createElementNS(SVG_NAMESPACE, 'g');
614614
healthPillGroup.classList.add('health-pill');
615615

616616
// Define the gradient for the health pill.
617-
let healthPillDefs = document.createElementNS(svgNamespace, 'defs');
617+
let healthPillDefs = document.createElementNS(SVG_NAMESPACE, 'defs');
618618
healthPillGroup.appendChild(healthPillDefs);
619619
let healthPillGradient =
620-
document.createElementNS(svgNamespace, 'linearGradient');
620+
document.createElementNS(SVG_NAMESPACE, 'linearGradient');
621621

622622
// Every element in a web page must have a unique ID.
623623
const healthPillGradientId = 'health-pill-gradient-' + healthPillId;
@@ -633,13 +633,13 @@ function _addHealthPill(
633633
cumulativeCount += lastHealthPillElementsBreakdown[i];
634634

635635
// Create a color interval using 2 stop elements.
636-
let stopElement0 = document.createElementNS(svgNamespace, 'stop');
636+
let stopElement0 = document.createElementNS(SVG_NAMESPACE, 'stop');
637637
stopElement0.setAttribute('offset', previousOffset);
638638
stopElement0.setAttribute(
639639
'stop-color', healthPillEntries[i].background_color);
640640
healthPillGradient.appendChild(stopElement0);
641641

642-
let stopElement1 = document.createElementNS(svgNamespace, 'stop');
642+
let stopElement1 = document.createElementNS(SVG_NAMESPACE, 'stop');
643643
let percent = (cumulativeCount * 100 / totalCount) + '%';
644644
stopElement1.setAttribute('offset', percent);
645645
stopElement1.setAttribute(
@@ -650,14 +650,14 @@ function _addHealthPill(
650650
healthPillDefs.appendChild(healthPillGradient);
651651

652652
// Create the rectangle for the health pill.
653-
let rect = document.createElementNS(svgNamespace, 'rect');
653+
let rect = document.createElementNS(SVG_NAMESPACE, 'rect');
654654
rect.setAttribute('fill', 'url(#' + healthPillGradientId + ')');
655655
rect.setAttribute('width', String(healthPillWidth));
656656
rect.setAttribute('height', String(healthPillHeight));
657657
healthPillGroup.appendChild(rect);
658658

659659
// Show a title with specific counts on hover.
660-
let titleSvg = document.createElementNS(svgNamespace, 'title');
660+
let titleSvg = document.createElementNS(SVG_NAMESPACE, 'title');
661661
titleSvg.textContent = _getHealthPillTextContent(
662662
healthPill, totalCount, lastHealthPillElementsBreakdown, numericStats);
663663
healthPillGroup.appendChild(titleSvg);
@@ -694,7 +694,7 @@ function _addHealthPill(
694694
}
695695
}
696696

697-
let statsSvg = document.createElementNS(svgNamespace, 'text');
697+
let statsSvg = document.createElementNS(SVG_NAMESPACE, 'text');
698698
const minString =
699699
humanizeHealthPillStat(numericStats.min, shouldRoundOnesDigit);
700700
const maxString =

0 commit comments

Comments
 (0)