| // Position of the vertical axis. |
| const X_LINE = 50; |
| // General-use margin size. |
| const MARGIN = 5; |
| // Position of the horizontal axis, relative to the bottom. |
| const Y_LINE = 35; |
| // Minimum distance between time tick labels. |
| const MIN_TICK_DIST = 50; |
| // Radius for rounded rectangle corners. |
| const RADIUS = 3; |
| // Height of unit boxes. |
| const BOX_HEIGHT = 25; |
| // Distance between Y tick marks on the unit graph. |
| const Y_TICK_DIST = BOX_HEIGHT + 2; |
| // Rects used for mouseover detection. |
| // Objects of {x, y, x2, y2, i} where `i` is the index into UNIT_DATA. |
| let HIT_BOXES = []; |
| // Index into UNIT_DATA of the last unit hovered over by mouse. |
| let LAST_HOVER = null; |
| // Key is unit index, value is {x, y, width, sections} of the box. |
| let UNIT_COORDS = {}; |
| // Map of unit index to the index it was unlocked by. |
| let REVERSE_UNIT_DEPS = {}; |
| let REVERSE_UNIT_RMETA_DEPS = {}; |
| let UNIT_BY_INDEX = {}; |
| |
| const MIN_GRAPH_WIDTH = 200; |
| const MAX_GRAPH_WIDTH = 4096; |
| |
| // How many pixels per second is added by each scale value |
| const SCALE_PIXELS_PER_SEC = 8; |
| |
| function scale_to_graph_width(scale) { |
| // The scale corresponds to `SCALE_PIXELS_PER_SEC` pixels per seconds. |
| // We thus multiply it by that, and the total duration, to get the graph width. |
| const width = scale * SCALE_PIXELS_PER_SEC * DURATION; |
| |
| // We then cap the size of the graph. It is hard to view if it is too large, and |
| // browsers may not render a large graph because it takes too much memory. |
| // 4096 is still ridiculously large, and probably won't render on mobile |
| // browsers, but should be ok for many desktop environments. |
| // Also use a minimum width of 200. |
| return Math.max(MIN_GRAPH_WIDTH, Math.min(MAX_GRAPH_WIDTH, width)); |
| } |
| |
| // This function performs the reverse of `scale_to_graph_width`. |
| function width_to_graph_scale(width) { |
| const maxWidth = Math.min(MAX_GRAPH_WIDTH, width); |
| const minWidth = Math.max(MIN_GRAPH_WIDTH, width); |
| |
| const trimmedWidth = Math.max(minWidth, Math.min(maxWidth, width)); |
| |
| const scale = Math.round(trimmedWidth / (DURATION * SCALE_PIXELS_PER_SEC)); |
| return Math.max(1, scale); |
| } |
| |
| // Init scale value and limits based on the client's window width and min/max graph width. |
| const scaleElement = document.getElementById('scale'); |
| scaleElement.min = width_to_graph_scale(MIN_GRAPH_WIDTH); |
| scaleElement.max = width_to_graph_scale(MAX_GRAPH_WIDTH); |
| scaleElement.value = width_to_graph_scale(window.innerWidth * 0.75); |
| |
| // Colors from css |
| const getCssColor = name => getComputedStyle(document.body).getPropertyValue(name); |
| const TEXT_COLOR = getCssColor('--text'); |
| const BG_COLOR = getCssColor('--background'); |
| const CANVAS_BG = getCssColor('--canvas-background'); |
| const AXES_COLOR = getCssColor('--canvas-axes'); |
| const GRID_COLOR = getCssColor('--canvas-grid'); |
| const CODEGEN_COLOR = getCssColor('--canvas-codegen'); |
| const LINK_COLOR = getCssColor('--canvas-link'); |
| // Final leftover section after link |
| const OTHER_COLOR = getCssColor('--canvas-other'); |
| const CUSTOM_BUILD_COLOR = getCssColor('--canvas-custom-build'); |
| const NOT_CUSTOM_BUILD_COLOR = getCssColor('--canvas-not-custom-build'); |
| const DEP_LINE_COLOR = getCssColor('--canvas-dep-line'); |
| const DEP_LINE_HIGHLIGHTED_COLOR = getCssColor('--canvas-dep-line-highlighted'); |
| const CPU_COLOR = getCssColor('--canvas-cpu'); |
| |
| for (const unit of UNIT_DATA) { |
| UNIT_BY_INDEX[unit.i] = unit; |
| for (let unblocked of unit.unblocked_units) { |
| REVERSE_UNIT_DEPS[unblocked] = unit.i; |
| } |
| for (let unblocked of unit.unblocked_rmeta_units) { |
| REVERSE_UNIT_RMETA_DEPS[unblocked] = unit.i; |
| } |
| } |
| |
| // Compute x,y coordinate of each block. |
| // We also populate a map with the count of each unit name to disambiguate if necessary |
| function compute_unit_coords(units, px_per_sec) { |
| const unitCount = new Map(); |
| UNIT_COORDS = {}; |
| for (i = 0; i < units.length; i++) { |
| let unit = units[i]; |
| let y = i * Y_TICK_DIST + 1; |
| let x = px_per_sec * unit.start; |
| |
| const sections = []; |
| if (unit.sections !== null) { |
| // We have access to compilation sections |
| for (const section of unit.sections) { |
| const [name, { start, end }] = section; |
| sections.push({ |
| name, |
| start: x + px_per_sec * start, |
| width: (end - start) * px_per_sec |
| }); |
| } |
| } |
| |
| let width = Math.max(px_per_sec * unit.duration, 1.0); |
| UNIT_COORDS[unit.i] = { x, y, width, sections }; |
| |
| const count = unitCount.get(unit.name) || 0; |
| unitCount.set(unit.name, count + 1); |
| } |
| |
| return { UNIT_COORDS, unitCount } |
| } |
| |
| class SvgRenderer { |
| constructor() {} |
| |
| render_pipeline_graph() { |
| if (UNIT_DATA.length == 0) { |
| return; |
| } |
| HIT_BOXES.length = 0; |
| const min_time = document.getElementById('min-unit-time').valueAsNumber; |
| |
| const units = UNIT_DATA.filter(unit => unit.duration >= min_time); |
| |
| const graph_height = Y_TICK_DIST * units.length; |
| let { canvas_height, canvas_width, graph_width, px_per_sec } = graph_dimension(graph_height); |
| const axes = this._draw_graph_axes({ canvas_height, graph_height, graph_width, px_per_sec }); |
| |
| // Draw Y tick marks. |
| let y_ticks = ''; |
| if (units.length != 0) { |
| const path_commands = Array(units.length - 1) |
| .fill(0) |
| .map((_, idx) => { |
| const n = idx + 1; |
| const y = MARGIN + Y_TICK_DIST * n; |
| return `M${X_LINE} ${y} h-5`; |
| }); |
| y_ticks = `<path class="axis axis-y" d="${path_commands.join(" ")}"></path>`; |
| } |
| |
| // Draw Y labels. |
| const y_labels = Array(units.length) |
| .fill(0) |
| .map((_, n) => { |
| let y = MARGIN + Y_TICK_DIST * n + Y_TICK_DIST / 2; |
| return `<text class="axis axis-y" x="${X_LINE - 4}" y="${y}">${n + 1}</text>`; |
| }) |
| .join(""); |
| |
| // Draw the graph. |
| const { UNIT_COORDS, unitCount } = compute_unit_coords(units, px_per_sec); |
| const presentSections = new Set(); |
| |
| // Draw the blocks. |
| let blocks = Array(units.length) |
| .fill(0) |
| .map((_, i) => { |
| let unit = units[i]; |
| let { x, y, width, sections } = UNIT_COORDS[unit.i]; |
| |
| const boxes = [this._roundedRect("", x, y, width, unit)].concat( |
| sections.map((section) => { |
| presentSections.add(section.name); |
| return this._roundedRect( |
| section.name, |
| section.start, |
| y, |
| section.width, |
| unit, |
| ); |
| }), |
| ); |
| |
| const labelName = (unitCount.get(unit.name) || 0) > 1 ? `${unit.name} (v${unit.version})${unit.target}` : `${unit.name}${unit.target}`; |
| const label = `${labelName}: ${unit.duration}s`; |
| |
| const text_info_width = measure_text_width(label); |
| const label_x = Math.min(x + 5.0, canvas_width - text_info_width - X_LINE); |
| const box_label = `<text class="box" x="${label_x}" y="${y + BOX_HEIGHT / 2}">${label}</text>`; |
| |
| const dep_lines = this._draw_dep_lines(unit.i); |
| return boxes.concat(box_label).concat(dep_lines); |
| }) |
| .flat() |
| .join(""); |
| |
| blocks = ` |
| <g id="boxes" transform="translate(${X_LINE} ${MARGIN})">${blocks}</g> |
| <g id="hl-dep-lines" transform="translate(${X_LINE} ${MARGIN})"></g>`; |
| |
| // Draw a legend. |
| const legend = ` |
| <g class="legend" transform="translate(${canvas_width-200},${MARGIN})"> |
| ${this._draw_legend(160, get_legend_entries(presentSections))} |
| </g>`; |
| |
| const svg_container = document.getElementById("pipeline-container"); |
| if (svg_container) { |
| const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); |
| svg.setAttribute("width", canvas_width); |
| svg.setAttribute("height", canvas_height); |
| Object.assign(svg.style, { |
| background: CANVAS_BG, |
| color: TEXT_COLOR, |
| fontFamily: 'sans-serif', |
| fontSize: '16px' |
| }); |
| svg.innerHTML = ` |
| <style type="text/css"> |
| path.axis { |
| fill: none; |
| stroke: ${AXES_COLOR}; |
| stroke-width: 2; |
| } |
| text.axis { |
| fill: ${AXES_COLOR}; |
| font-size: 16px; |
| stroke: none; |
| text-anchor: middle; |
| } |
| text.axis-y { |
| dominant-baseline: middle; |
| text-anchor: end; |
| } |
| .grid { |
| stroke: ${GRID_COLOR}; |
| stroke-dasharray: 2 4; |
| stroke-width: 2; |
| } |
| rect.box { |
| fill: ${NOT_CUSTOM_BUILD_COLOR}; |
| height: ${BOX_HEIGHT}px; |
| rx: ${RADIUS}px; |
| ry: ${RADIUS}px; |
| } |
| rect.box.run-custom-build { |
| fill: ${CUSTOM_BUILD_COLOR}; |
| } |
| rect.box.codegen { |
| fill: ${CODEGEN_COLOR}; |
| } |
| rect.box.link { |
| fill: ${LINK_COLOR}; |
| } |
| rect.box.other { |
| fill: ${OTHER_COLOR} |
| } |
| text.box { |
| dominant-baseline: middle; |
| fill: ${TEXT_COLOR}; |
| font-size: 14px; |
| text-anchor: start |
| } |
| .dep-line { |
| fill: none; |
| stroke: ${DEP_LINE_COLOR}; |
| stroke-dasharray: 2; |
| stroke-width: 2; |
| } |
| #hl-dep-lines .dep-line{ |
| stroke: ${DEP_LINE_HIGHLIGHTED_COLOR}; |
| } |
| path.legend__cell { |
| stroke-width: 2; |
| } |
| .legend__desc { |
| fill: ${TEXT_COLOR}; |
| dominant-baseline: middle; |
| } |
| </style> |
| ${axes} |
| ${y_ticks} |
| ${y_labels} |
| ${blocks} |
| ${legend}`; |
| svg_container.innerHTML = svg.outerHTML; |
| } |
| } |
| |
| // Draw a legend at the current position of the ctx. |
| // entries should be an array of objects with the following scheme: |
| // { |
| // "name": <name of the legend entry> [string], |
| // "color": <color of the legend entry> [string], |
| // "line": <should the entry be a thin line or a rectangle> [bool] |
| // } |
| _draw_legend(width, entries) { |
| const entry_height = 20; |
| |
| // Add a bit of margin to the bottom and top |
| const height = entries.length * entry_height + 4; |
| |
| // Draw background |
| let legend = []; |
| legend.push(`<rect stroke="${TEXT_COLOR}" stroke-width="1" fill="${BG_COLOR}" width="${width}" height="${height}"></rect>`); |
| |
| // Dimension of a block |
| const block_height = 15; |
| const block_width = 30; |
| |
| // Margin from the left edge |
| const x_start = 5; |
| // Width of the "mark" section (line/block) |
| const mark_width = 45; |
| |
| // Draw legend entries |
| let y = 12; |
| for (const entry of entries) { |
| const cell = entry.line ? |
| `<path class="legend__cell" stroke="${entry.color}" d="M${x_start} ${y} h ${mark_width}"></path>` : |
| ` |
| <rect class="legend__cell" fill="${entry.color}" width="${block_width}" height="${block_height}" |
| x="${x_start + (mark_width - block_width) / 2}" y="${y - (block_height / 2)}" |
| ></rect>`; |
| legend.push(cell); |
| legend.push(`<text class="legend__desc" x="${x_start + mark_width + 4}" y="${y + 1}">${entry.name}</text>`) |
| y += entry_height; |
| } |
| return legend.join("") |
| } |
| |
| // Draws lines from the given unit to the units it unlocks. |
| _draw_dep_lines(unit_idx) { |
| const unit = UNIT_BY_INDEX[unit_idx]; |
| const { x, y, sections } = UNIT_COORDS[unit_idx]; |
| return unit.unblocked_units |
| .map((unblocked) => this._draw_one_dep_line(x, y, unblocked, unit_idx)) |
| .concat( |
| unit.unblocked_rmeta_units.map((unblocked) => { |
| const codegen_x = get_codegen_section_x(sections); |
| return this._draw_one_dep_line(codegen_x, y, unblocked, unit_idx); |
| }), |
| ) |
| .join(""); |
| } |
| |
| _draw_one_dep_line(from_x, from_y, to_unit, from_unit) { |
| if (to_unit in UNIT_COORDS) { |
| let { x: u_x, y: u_y } = UNIT_COORDS[to_unit]; |
| // Use multiple path instead to avoid potential rendering issue while scrolling in Firefox. |
| return ` |
| <path class="dep-line" data-unblocked="${to_unit}" data-unblocked-by="${from_unit}" d="M ${from_x} ${from_y + BOX_HEIGHT / 2} h -5"></path> |
| <path class="dep-line" data-unblocked="${to_unit}" data-unblocked-by="${from_unit}" d="M ${from_x - 5} ${from_y + BOX_HEIGHT / 2} V ${u_y + BOX_HEIGHT / 2}"></path> |
| <path class="dep-line" data-unblocked="${to_unit}" data-unblocked-by="${from_unit}" d="M ${from_x - 5} ${u_y + BOX_HEIGHT / 2} H${u_x}"></path> |
| `; |
| } |
| } |
| |
| render_timing_graph() { |
| if (CONCURRENCY_DATA.length == 0) { |
| return; |
| } |
| const HEIGHT = 400; |
| const AXIS_HEIGHT = HEIGHT - MARGIN - Y_LINE; |
| const TOP_MARGIN = 10; |
| const GRAPH_HEIGHT = AXIS_HEIGHT - TOP_MARGIN; |
| |
| let { canvas_height, canvas_width, graph_height, graph_width, px_per_sec } = graph_dimension(AXIS_HEIGHT); |
| const axis_bottom = this._draw_graph_axes({ canvas_height, graph_height, graph_width, px_per_sec }); |
| |
| // Draw Y tick marks and labels. |
| let max_v = 0; |
| for (let c of CONCURRENCY_DATA) { |
| max_v = Math.max(max_v, c.active, c.waiting, c.inactive); |
| } |
| const px_per_v = GRAPH_HEIGHT / max_v; |
| const { step, tick_dist, num_ticks } = split_ticks(max_v, px_per_v, GRAPH_HEIGHT); |
| |
| const labels = []; |
| const ticks_path_commands = Array(num_ticks) |
| .fill(0) |
| .map((_, n) => { |
| const y = HEIGHT - Y_LINE - ((n + 1) * tick_dist); |
| labels.push( `<text class="axis axis-y" x="${X_LINE - 10}" y="${y}">${(n + 1) * step}</text>`); |
| return `M${X_LINE} ${y} h-5`; |
| }) |
| .join(" "); |
| const ticks = `<path class="axis axis-y" d="${ticks_path_commands}"></path>`; |
| const ticks_labels = labels.join(""); |
| |
| // Label the Y axis. |
| let label_y = (HEIGHT - Y_LINE) / 2; |
| |
| const y_axis_label = `<text class="axis axis-label" transform="translate(15, ${label_y}) rotate(270)"># Units</text>` |
| const axis_left = `${y_axis_label}${ticks}${ticks_labels}`; |
| |
| // Draw the graph. |
| |
| function coord(t, v) { |
| return { |
| x: graph_width * (t / DURATION), |
| y: TOP_MARGIN + GRAPH_HEIGHT * (1.0 - (v / max_v)) |
| }; |
| } |
| |
| const cpuFillStyle = CPU_COLOR; |
| let cpu = ""; |
| if (CPU_USAGE.length > 1) { |
| const bottomLeft = coord(CPU_USAGE[0][0], 0); |
| const bottomRight = coord(CPU_USAGE[CPU_USAGE.length - 1][0], 0); |
| const path_commands = [`M${bottomLeft.x} ${bottomLeft.y}`] |
| .concat( |
| Array(CPU_USAGE.length) |
| .fill(0) |
| .map((_, i) => { |
| let [time, usage] = CPU_USAGE[i]; |
| let { x, y } = coord(time, (usage / 100.0) * max_v); |
| return `L${x} ${y}`; |
| }), |
| ) |
| .concat(`L${bottomRight.x} ${bottomRight.y}`); |
| cpu = `<path stroke-width="0" fill="${cpuFillStyle}" d="${path_commands.join("")}"></path>`; |
| } |
| |
| function draw_line(style, key) { |
| let first = CONCURRENCY_DATA[0]; |
| let last = coord(first.t, key(first)); |
| const path_commands = []; |
| path_commands.push(`M${last.x} ${last.y}`) |
| for (let i = 1; i < CONCURRENCY_DATA.length; i++) { |
| let c = CONCURRENCY_DATA[i]; |
| let { x, y } = coord(c.t, key(c)); |
| path_commands.push(`L${x} ${last.y} V${y}`); |
| last = { x, y }; |
| } |
| return `<path class="status-line" stroke="${style}" d="${path_commands.join("")}"></path>` |
| } |
| |
| const lines = []; |
| lines.push(draw_line('blue', function(c) { return c.inactive; })); |
| lines.push(draw_line('red', function(c) { return c.waiting; })); |
| lines.push(draw_line('green', function(c) { return c.active; })); |
| |
| const timings = `<g class="graph-content" transform="translate(${X_LINE} ${MARGIN})">${cpu}${lines}</g>`; |
| |
| // Draw a legend. |
| const legend = ` |
| <g class="legend" transform="translate(${canvas_width-200},${MARGIN})"> |
| ${this._draw_legend(150, [{ |
| name: "Waiting", |
| color: "red", |
| line: true |
| }, { |
| name: "Inactive", |
| color: "blue", |
| line: true |
| }, { |
| name: "Active", |
| color: "green", |
| line: true |
| }, { |
| name: "CPU Usage", |
| color: cpuFillStyle, |
| line: false |
| }])} |
| </g>`; |
| |
| const svg_container = document.getElementById("timing-container"); |
| if (svg_container) { |
| const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); |
| svg.setAttribute("width", canvas_width); |
| svg.setAttribute("height", canvas_height); |
| Object.assign(svg.style, { |
| background: CANVAS_BG, |
| color: TEXT_COLOR, |
| fontFamily: 'sans-serif', |
| fontSize: '16px' |
| }); |
| svg.innerHTML = ` |
| <style type="text/css"> |
| path.axis { |
| fill: none; |
| stroke: ${AXES_COLOR}; |
| stroke-width: 2; |
| } |
| text.axis { |
| fill: ${AXES_COLOR}; |
| font-size: 16px; |
| stroke: none; |
| text-anchor: middle; |
| } |
| text.axis-y { |
| dominant-baseline: middle; |
| text-anchor: end; |
| } |
| .grid { |
| stroke: ${GRID_COLOR}; |
| stroke-dasharray: 2 4; |
| stroke-width: 2; |
| } |
| .status-line { |
| fill: none; |
| stroke-width: 2; |
| } |
| path.legend__cell { |
| stroke-width: 2; |
| } |
| .legend__desc { |
| fill: ${TEXT_COLOR}; |
| dominant-baseline: middle; |
| } |
| </style> |
| ${axis_bottom} |
| ${axis_left} |
| ${timings} |
| ${legend} |
| `; |
| svg_container.innerHTML = svg.outerHTML; |
| } |
| } |
| |
| _draw_graph_axes({ canvas_height, graph_height, graph_width, px_per_sec }) { |
| // Draw main axes. |
| const main_axes = ` |
| <path class="axis" d=" |
| M${X_LINE} ${MARGIN} |
| V${graph_height + MARGIN} |
| H${X_LINE + graph_width + 20}" |
| ></path>`; |
| |
| // Draw X tick marks. |
| const { step, tick_dist, num_ticks } = split_ticks(DURATION, px_per_sec, graph_width); |
| const TICK_LENGTH = 5; |
| const grid_height = canvas_height - Y_LINE; |
| const labels = []; |
| const ticks_path_commands = Array(num_ticks) |
| .fill(0) |
| .map((_, n) => { |
| const x = X_LINE + ((n + 1) * tick_dist); |
| const label = ` |
| <text class="axis" x="${x}" y="${grid_height + 20}"> |
| ${(n + 1) * step}s |
| </text>`; |
| labels.push(label); |
| return `M${x} ${grid_height} v${TICK_LENGTH}`; |
| }) |
| .join(" "); |
| |
| const ticks = `<path class="axis" d="${ticks_path_commands}"></path>`; |
| const ticks_labels = labels.join("") |
| |
| // Draw vertical lines. |
| const vertical_lines = Array(num_ticks) |
| .fill(0) |
| .map((_, n) => { |
| const x = X_LINE + ((n + 1) * tick_dist); |
| return `<path class="grid" d="M${x} ${grid_height - 1} V${MARGIN}"></path>`; |
| }) |
| .join(""); |
| return `${main_axes}${ticks}${vertical_lines}${ticks_labels}`; |
| } |
| |
| _roundedRect(section_name, x, y, width, unit) { |
| return ` |
| <rect class="box ${unit.mode} ${section_name}" data-i="${unit.i}" x="${x}" y="${y}" width="${width}" |
| onmousemove="hl(${unit.i})" |
| > |
| </rect>` |
| } |
| } |
| |
| // Determine the width of text for svg. |
| function measure_text_width(text) { |
| let ctx; |
| let canvas = measure_text_width.canvas; |
| if (!canvas) { |
| canvas = (measure_text_width.canvas = document.createElement("canvas")); |
| } |
| ctx = canvas.getContext("2d"); |
| ctx.font = '14px sans-serif'; |
| const text_info = ctx.measureText(text); |
| return text_info.width; |
| } |
| |
| // Gets the x-coordinate of the codegen section of a unit. |
| // |
| // This is for drawing rmeta dependency lines. |
| function get_codegen_section_x(sections) { |
| const codegen_section = sections.find(s => s.name === "codegen") |
| if (!codegen_section) { |
| // This happens only when type-checking (e.g., `cargo check`) |
| return null; |
| } |
| return codegen_section.start; |
| } |
| |
| function get_legend_entries(presentSections) { |
| let frontend_name = "Frontend/rest"; |
| if (presentSections.has("other")) { |
| frontend_name = "Frontend"; |
| } |
| |
| const legend_entries = [{ |
| name: frontend_name, |
| color: NOT_CUSTOM_BUILD_COLOR, |
| line: false |
| }]; |
| if (presentSections.has("codegen")) { |
| legend_entries.push({ |
| name: "Codegen", |
| color: CODEGEN_COLOR, |
| line: false |
| }); |
| } |
| if (presentSections.has("link")) { |
| legend_entries.push({ |
| name: "Linking", |
| color: LINK_COLOR, |
| line: false |
| }); |
| } |
| if (presentSections.has("other")) { |
| legend_entries.push({ |
| name: "Other", |
| color: OTHER_COLOR, |
| line: false |
| }); |
| } |
| return legend_entries |
| } |
| |
| function graph_dimension(graph_height) { |
| const scale = document.getElementById('scale').valueAsNumber; |
| const graph_width = scale_to_graph_width(scale); |
| const px_per_sec = graph_width / DURATION; |
| const canvas_width = Math.max(graph_width + X_LINE + 30, X_LINE + 250); |
| const canvas_height = graph_height + MARGIN + Y_LINE; |
| return { canvas_height, canvas_width, graph_height, graph_width, px_per_sec, scale } |
| } |
| |
| // Determine the spacing and number of ticks along an axis. |
| function split_ticks(max_value, px_per_v, max_px) { |
| const max_ticks = Math.floor(max_px / MIN_TICK_DIST); |
| if (max_ticks <= 1) { |
| // Graph is too small for even 1 tick. |
| return {step: max_value, tick_dist: max_px, num_ticks: 1}; |
| } |
| let step; |
| if (max_value <= max_ticks) { |
| step = 1; |
| } else if (max_value <= max_ticks * 2) { |
| step = 2; |
| } else if (max_value <= max_ticks * 4) { |
| step = 4; |
| } else if (max_value <= max_ticks * 5) { |
| step = 5; |
| } else { |
| step = 10; |
| let count = 0; |
| while (true) { |
| if (count > 100) { |
| throw Error("tick loop too long"); |
| } |
| count += 1; |
| if (max_value <= max_ticks * step) { |
| break; |
| } |
| step += 10; |
| } |
| } |
| const tick_dist = px_per_v * step; |
| const num_ticks = Math.floor(max_value / step); |
| return {step, tick_dist, num_ticks}; |
| } |
| |
| function hl(i) { |
| // Highlight dependency lines on mouse hover. |
| if (i === undefined || i == LAST_HOVER) { |
| return |
| } |
| LAST_HOVER = i; |
| let g = document.getElementById("hl-dep-lines"); |
| |
| if (g) { |
| const selector = '#boxes .dep-line[data-unblocked="'+i+'"],#boxes .dep-line[data-unblocked-by="' + i + '"]'; |
| const lines = Array.from(document.querySelectorAll(selector)).map(el => el.outerHTML); |
| g.innerHTML = lines.join(""); |
| } |
| } |
| |
| let renderer = new SvgRenderer(); |
| try { |
| renderer.render_pipeline_graph(); |
| renderer.render_timing_graph(); |
| } catch (err) { |
| console.error(err); |
| } |
| |
| // Set up and handle controls. |
| { |
| const range = document.getElementById('min-unit-time'); |
| const time_output = document.getElementById('min-unit-time-output'); |
| time_output.innerHTML = `${range.value}s`; |
| range.oninput = event => { |
| time_output.innerHTML = `${range.value}s`; |
| try { |
| renderer.render_pipeline_graph(); |
| } catch (err) { |
| console.error(err); |
| } |
| }; |
| |
| const scale = document.getElementById('scale'); |
| const scale_output = document.getElementById('scale-output'); |
| scale_output.innerHTML = `${scale.value}`; |
| scale.oninput = event => { |
| scale_output.innerHTML = `${scale.value}`; |
| try { |
| renderer.render_pipeline_graph(); |
| renderer.render_timing_graph(); |
| } catch (err) { |
| console.error(err); |
| } |
| }; |
| |
| Array.from(document.querySelectorAll("input[name='renderer']")).forEach(function(el) { |
| el.addEventListener("change", function(event) { |
| renderer = setupRenderer(event.target.value); |
| try { |
| renderer.render_pipeline_graph(); |
| renderer.render_timing_graph(); |
| } catch (err) { |
| console.error(err); |
| } |
| }); |
| }); |
| } |