private buildDom()

in pxtsim/visuals/breadboard.ts [362:656]


        private buildDom() {
            this.bb = <SVGSVGElement>svg.elt("svg", {
                "version": "1.0",
                "viewBox": `0 0 ${WIDTH} ${HEIGHT}`,
                "class": `sim-bb`,
                "width": WIDTH + "px",
                "height": HEIGHT + "px",
            });
            this.styleEl = <SVGStyleElement>svg.child(this.bb, "style", {});
            this.styleEl.textContent += BREADBOARD_CSS;
            this.defs = <SVGDefsElement>svg.child(this.bb, "defs", {});

            //background
            svg.child(this.bb, "rect", { class: "sim-bb-background", width: WIDTH, height: HEIGHT, rx: BACKGROUND_ROUNDING, ry: BACKGROUND_ROUNDING });

            //mid channel
            let channelGid = "sim-bb-channel-grad";
            let channelGrad = <SVGLinearGradientElement>svg.elt("linearGradient")
            svg.hydrate(channelGrad, { id: channelGid, x1: "0%", y1: "0%", x2: "0%", y2: "100%" });
            this.defs.appendChild(channelGrad);
            let channelDark = "#AAA";
            let channelLight = "#CCC";
            let stop1 = svg.child(channelGrad, "stop", { offset: "0%", style: `stop-color: ${channelDark};` })
            let stop2 = svg.child(channelGrad, "stop", { offset: "20%", style: `stop-color: ${channelLight};` })
            let stop3 = svg.child(channelGrad, "stop", { offset: "80%", style: `stop-color: ${channelLight};` })
            let stop4 = svg.child(channelGrad, "stop", { offset: "100%", style: `stop-color: ${channelDark};` })

            const mkChannel = (cy: number, h: number, cls?: string) => {
                let channel = svg.child(this.bb, "rect", { class: `sim-bb-channel ${cls || ""}`, y: cy - h / 2, width: WIDTH, height: h });
                channel.setAttribute("fill", `url(#${channelGid})`);
                return channel;
            }

            mkChannel(BAR_HEIGHT + MID_HEIGHT / 2, CHANNEL_HEIGHT, "sim-bb-mid-channel");
            mkChannel(BAR_HEIGHT, SMALL_CHANNEL_HEIGHT, "sim-bb-sml-channel");
            mkChannel(BAR_HEIGHT + MID_HEIGHT, SMALL_CHANNEL_HEIGHT, "sim-bb-sml-channel");

            //-----pins
            const getMidTopOrBot = (rowIdx: number) => rowIdx < BREADBOARD_MID_ROWS / 2.0 ? "b" : "t";
            const getBarTopOrBot = (colIdx: number) => colIdx < POWER_COLS / 2.0 ? "b" : "t";
            const getMidGroupName = (rowIdx: number, colIdx: number) => {
                let botOrTop = getMidTopOrBot(rowIdx);
                let colNm = getColumnName(colIdx);
                return `${botOrTop}${colNm}`;
            };
            const getBarRowName = (rowIdx: number) => rowIdx === 0 ? "-" : "+";
            const getBarGroupName = (rowIdx: number, colIdx: number) => {
                let botOrTop = getBarTopOrBot(colIdx);
                let rowName = getBarRowName(rowIdx);
                return `${rowName}${botOrTop}`;
            };

            //mid grid
            let midGridRes = mkGrid({
                xOffset: MID_GRID_X,
                yOffset: MID_GRID_Y,
                rowCount: BREADBOARD_MID_ROWS,
                colCount: BREADBOARD_MID_COLS,
                pinDist: PIN_DIST,
                mkPin: mkBBPin,
                mkHoverPin: mkBBHoverPin,
                getRowName: getRowName,
                getColName: getColumnName,
                getGroupName: getMidGroupName,
                rowIdxsWithGap: MID_ROW_GAPS,
            });
            let midGridG = midGridRes.g;
            this.allPins = this.allPins.concat(midGridRes.allPins);

            //bot bar
            let botBarGridRes = mkGrid({
                xOffset: BAR_BOT_GRID_X,
                yOffset: BAR_BOT_GRID_Y,
                rowCount: BAR_ROWS,
                colCount: BAR_COLS,
                pinDist: PIN_DIST,
                mkPin: mkBBPin,
                mkHoverPin: mkBBHoverPin,
                getRowName: getBarRowName,
                getColName: getColumnName,
                getGroupName: getBarGroupName,
                colIdxsWithGap: BAR_COL_GAPS,
            });
            let botBarGridG = botBarGridRes.g;
            this.allPins = this.allPins.concat(botBarGridRes.allPins);

            //top bar
            let topBarGridRes = mkGrid({
                xOffset: BAR_TOP_GRID_X,
                yOffset: BAR_TOP_GRID_Y,
                rowCount: BAR_ROWS,
                colCount: BAR_COLS,
                colStartIdx: BAR_COLS,
                pinDist: PIN_DIST,
                mkPin: mkBBPin,
                mkHoverPin: mkBBHoverPin,
                getRowName: getBarRowName,
                getColName: getColumnName,
                getGroupName: getBarGroupName,
                colIdxsWithGap: BAR_COL_GAPS.map(g => g + BAR_COLS),
            });
            let topBarGridG = topBarGridRes.g;
            this.allPins = this.allPins.concat(topBarGridRes.allPins);

            //tooltip
            this.allPins.forEach(pin => {
                let { el, row, col, hoverEl } = pin
                let title = `(${row},${col})`;
                svg.hydrate(el, { title: title });
                svg.hydrate(hoverEl, { title: title });
            })

            //catalog pins
            this.allPins.forEach(pin => {
                let colToPin = this.rowColToPin[pin.row];
                if (!colToPin)
                    colToPin = this.rowColToPin[pin.row] = {};
                colToPin[pin.col] = pin;
            })

            //-----labels
            const mkBBLabelAtPin = (row: string, col: string, xOffset: number, yOffset: number, txt: string, group?: string): GridLabel => {
                let size = PIN_LBL_SIZE;
                let rotation = LBL_ROTATION;
                let loc = this.getCoord({ type: "breadboard", row: row, col: col });
                let [cx, cy] = loc;
                let t = mkBBLabel(cx + xOffset, cy + yOffset, size, rotation, txt, group);
                return t;
            }

            //columns
            for (let colIdx = 0; colIdx < BREADBOARD_MID_COLS; colIdx++) {
                let colNm = getColumnName(colIdx);
                //top
                let rowTIdx = 0;
                let rowTNm = getRowName(rowTIdx);
                let groupT = getMidGroupName(rowTIdx, colIdx);
                let lblT = mkBBLabelAtPin(rowTNm, colNm, 0, -PIN_DIST, colNm, groupT);
                this.allLabels.push(lblT);
                //bottom
                let rowBIdx = BREADBOARD_MID_ROWS - 1;
                let rowBNm = getRowName(rowBIdx);
                let groupB = getMidGroupName(rowBIdx, colIdx);
                let lblB = mkBBLabelAtPin(rowBNm, colNm, 0, +PIN_DIST, colNm, groupB);
                this.allLabels.push(lblB);
            }
            //rows
            for (let rowIdx = 0; rowIdx < BREADBOARD_MID_ROWS; rowIdx++) {
                let rowNm = getRowName(rowIdx);
                //top
                let colTIdx = 0;
                let colTNm = getColumnName(colTIdx);
                let lblT = mkBBLabelAtPin(rowNm, colTNm, -PIN_DIST, 0, rowNm);
                this.allLabels.push(lblT);
                //top
                let colBIdx = BREADBOARD_MID_COLS - 1;
                let colBNm = getColumnName(colBIdx);
                let lblB = mkBBLabelAtPin(rowNm, colBNm, +PIN_DIST, 0, rowNm);
                this.allLabels.push(lblB);
            }

            //+- labels
            let botPowerLabels = [
                //BL
                mkBBLabel(0 + POWER_LBL_OFFSET + MINUS_LBL_EXTRA_OFFSET, BAR_HEIGHT + MID_HEIGHT + POWER_LBL_OFFSET, MINUS_LBL_SIZE, LBL_ROTATION, `-`, getBarGroupName(0, 0), [`sim-bb-blue`]),
                mkBBLabel(0 + POWER_LBL_OFFSET, BAR_HEIGHT + MID_HEIGHT + BAR_HEIGHT - POWER_LBL_OFFSET, PLUS_LBL_SIZE, LBL_ROTATION, `+`, getBarGroupName(1, 0), [`sim-bb-red`]),
                //BR
                mkBBLabel(WIDTH - POWER_LBL_OFFSET + MINUS_LBL_EXTRA_OFFSET, BAR_HEIGHT + MID_HEIGHT + POWER_LBL_OFFSET, MINUS_LBL_SIZE, LBL_ROTATION, `-`, getBarGroupName(0, BAR_COLS - 1), [`sim-bb-blue`]),
                mkBBLabel(WIDTH - POWER_LBL_OFFSET, BAR_HEIGHT + MID_HEIGHT + BAR_HEIGHT - POWER_LBL_OFFSET, PLUS_LBL_SIZE, LBL_ROTATION, `+`, getBarGroupName(1, BAR_COLS - 1), [`sim-bb-red`]),
            ];
            this.allLabels = this.allLabels.concat(botPowerLabels);
            let topPowerLabels = [
                //TL
                mkBBLabel(0 + POWER_LBL_OFFSET + MINUS_LBL_EXTRA_OFFSET, 0 + POWER_LBL_OFFSET, MINUS_LBL_SIZE, LBL_ROTATION, `-`, getBarGroupName(0, BAR_COLS), [`sim-bb-blue`]),
                mkBBLabel(0 + POWER_LBL_OFFSET, BAR_HEIGHT - POWER_LBL_OFFSET, PLUS_LBL_SIZE, LBL_ROTATION, `+`, getBarGroupName(1, BAR_COLS), [`sim-bb-red`]),
                //TR
                mkBBLabel(WIDTH - POWER_LBL_OFFSET + MINUS_LBL_EXTRA_OFFSET, 0 + POWER_LBL_OFFSET, MINUS_LBL_SIZE, LBL_ROTATION, `-`, getBarGroupName(0, POWER_COLS - 1), [`sim-bb-blue`]),
                mkBBLabel(WIDTH - POWER_LBL_OFFSET, BAR_HEIGHT - POWER_LBL_OFFSET, PLUS_LBL_SIZE, LBL_ROTATION, `+`, getBarGroupName(1, POWER_COLS - 1), [`sim-bb-red`]),
            ];
            this.allLabels = this.allLabels.concat(topPowerLabels);

            //catalog lbls
            let lblNmToLbls: Map<GridLabel[]> = {};
            this.allLabels.forEach(lbl => {
                let { el, txt } = lbl;
                let lbls = lblNmToLbls[txt] = lblNmToLbls[txt] || []
                lbls.push(lbl);
            });
            const isPowerPin = (pin: GridPin) => pin.row === "-" || pin.row === "+";
            this.allPins.forEach(pin => {
                let { row, col, group } = pin;
                let colToLbls = this.rowColToLbls[row] || (this.rowColToLbls[row] = {});
                let lbls = colToLbls[col] || (colToLbls[col] = []);
                if (isPowerPin(pin)) {
                    //power pins
                    let isBot = Number(col) <= BAR_COLS;
                    if (isBot)
                        botPowerLabels.filter(l => l.group == pin.group).forEach(l => lbls.push(l));
                    else
                        topPowerLabels.filter(l => l.group == pin.group).forEach(l => lbls.push(l));
                } else {
                    //mid pins
                    let rowLbls = lblNmToLbls[row];
                    rowLbls.forEach(l => lbls.push(l));
                    let colLbls = lblNmToLbls[col];
                    colLbls.forEach(l => lbls.push(l));
                }
            })

            //-----blue & red lines
            const lnLen = BAR_GRID_WIDTH + PIN_DIST * 1.5;
            const lnThickness = PIN_DIST / 5.0;
            const lnYOff = PIN_DIST * 0.6;
            const lnXOff = (lnLen - BAR_GRID_WIDTH) / 2.0;
            const mkPowerLine = (x: number, y: number, group: string, cls: string): BBBar => {
                let ln = <SVGRectElement>svg.elt("rect");
                svg.hydrate(ln, {
                    class: `sim-bb-bar ${cls}`,
                    x: x,
                    y: y - lnThickness / 2.0,
                    width: lnLen,
                    height: lnThickness
                });
                let bar: BBBar = { el: ln, group: group };
                return bar;
            }
            let barLines = [
                //top
                mkPowerLine(BAR_BOT_GRID_X - lnXOff, BAR_BOT_GRID_Y - lnYOff, getBarGroupName(0, POWER_COLS - 1), "sim-bb-blue"),
                mkPowerLine(BAR_BOT_GRID_X - lnXOff, BAR_BOT_GRID_Y + PIN_DIST + lnYOff, getBarGroupName(1, POWER_COLS - 1), "sim-bb-red"),
                //bot
                mkPowerLine(BAR_TOP_GRID_X - lnXOff, BAR_TOP_GRID_Y - lnYOff, getBarGroupName(0, 0), "sim-bb-blue"),
                mkPowerLine(BAR_TOP_GRID_X - lnXOff, BAR_TOP_GRID_Y + PIN_DIST + lnYOff, getBarGroupName(1, 0), "sim-bb-red"),
            ];
            this.allPowerBars = this.allPowerBars.concat(barLines);
            //attach power bars
            this.allPowerBars.forEach(b => this.bb.appendChild(b.el));

            //-----electrically connected groups
            //make groups
            let allGrpNms = this.allPins.map(p => p.group).filter((g, i, a) => a.indexOf(g) == i);
            let groups: SVGGElement[] = allGrpNms.map(grpNm => {
                let g = <SVGGElement>svg.elt("g");
                return g;
            });
            groups.forEach(g => pxsim.U.addClass(g, "sim-bb-pin-group"));
            groups.forEach((g, i) => pxsim.U.addClass(g, `group-${allGrpNms[i]}`));
            let grpNmToGroup: Map<SVGGElement> = {};
            allGrpNms.forEach((g, i) => grpNmToGroup[g] = groups[i]);
            //group pins and add connecting wire
            let grpNmToPins: Map<GridPin[]> = {};
            this.allPins.forEach((p, i) => {
                let g = p.group;
                let pins = grpNmToPins[g] || (grpNmToPins[g] = []);
                pins.push(p);
            });
            //connecting wire
            allGrpNms.forEach(grpNm => {
                let pins = grpNmToPins[grpNm];
                let [xs, ys] = [pins.map(p => p.cx), pins.map(p => p.cy)];
                let minFn = (arr: number[]) => arr.reduce((a, b) => a < b ? a : b);
                let maxFn = (arr: number[]) => arr.reduce((a, b) => a > b ? a : b);
                let [minX, maxX, minY, maxY] = [minFn(xs), maxFn(xs), minFn(ys), maxFn(ys)];
                let wire = svg.elt("rect");
                let width = Math.max(maxX - minX, 0.0001/*rects with no width aren't displayed*/);
                let height = Math.max(maxY - minY, 0.0001);
                svg.hydrate(wire, { x: minX, y: minY, width: width, height: height });
                pxsim.U.addClass(wire, "sim-bb-group-wire")
                let g = grpNmToGroup[grpNm];
                g.appendChild(wire);
            });
            //group pins
            this.allPins.forEach(p => {
                let g = grpNmToGroup[p.group];
                g.appendChild(p.el);
                g.appendChild(p.hoverEl);
            })
            //group lbls
            let miscLblGroup = <SVGGElement>svg.elt("g");
            svg.hydrate(miscLblGroup, { class: "sim-bb-group-misc" });
            groups.push(miscLblGroup);
            this.allLabels.forEach(l => {
                if (l.group) {
                    let g = grpNmToGroup[l.group];
                    g.appendChild(l.el);
                    g.appendChild(l.hoverEl);
                } else {
                    miscLblGroup.appendChild(l.el);
                    miscLblGroup.appendChild(l.hoverEl);
                }
            })

            //attach to bb
            groups.forEach(g => this.bb.appendChild(g)); //attach to breadboard
        }