Last active
May 17, 2025 19:59
-
-
Save mr-eyes/b6bb736b2f64a2c4ae3316ed6c52b834 to your computer and use it in GitHub Desktop.
Google Slides Agenda Tracking Bar Script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| function addOrUpdateAgendaBarWithProgressV5() { | |
| const pres = SlidesApp.getActivePresentation(); | |
| const slides = pres.getSlides(); | |
| // 0) Clear any existing timeline shapes on ALL slides | |
| slides.forEach(slide => { | |
| slide.getPageElements().forEach(el => { | |
| const t = el.getTitle && el.getTitle(); | |
| if (t && t.startsWith('AGENDA_')) { | |
| el.remove(); | |
| } | |
| }); | |
| }); | |
| // 1) Section definitions | |
| const sections = [ | |
| { name: 'Motivation', from: 2, to: 3 }, | |
| { name: 'Scientific Background', from: 4, to: 5 }, | |
| { name: 'Methods', from: 6, to: 7 }, | |
| { name: 'Use Case 1', from: 8, to: 11 }, | |
| { name: 'Use Case 2', from: 12, to: 15 }, | |
| { name: 'Future Directions', from: 16, to: 17 }, | |
| { name: 'PhD Thesis', from: 18, to: 50 } | |
| ]; | |
| // 2) Palette | |
| const baselineColor = '#B0BEC5'; | |
| const inactiveBarBg = '#ECEFF1'; | |
| const activeBarFill = '#42A5F5'; | |
| const inactiveCircleFill = '#FFFFFF'; | |
| const inactiveBorder = '#CFD8DC'; | |
| const inactiveLabelColor = '#90A4AE'; | |
| const activeLabelColor = '#42A5F5'; | |
| // 3) Layout constants (ultra-compact) | |
| const MARGIN = 8; // side margin | |
| const VSPACE = 20; // total vertical space | |
| const DIAMETER = 8; // circle size | |
| const BAR_HEIGHT = 3; // progress bar thickness | |
| const DOT_D = 3; // boundary dot size | |
| const W = pres.getPageWidth(); | |
| const H = pres.getPageHeight(); | |
| const usableW = W - MARGIN * 2; | |
| const slotW = usableW / sections.length; | |
| // 4) Vertical positions | |
| const centerY = H - MARGIN - (VSPACE / 2); | |
| const lineY = centerY; | |
| const circleY = lineY - (DIAMETER / 2); | |
| const barY = lineY + (DIAMETER / 2) + 1; | |
| const labelY = barY + BAR_HEIGHT - 5; // tucked up under the bar | |
| // 5) Draw on each slide | |
| slides.forEach((slide, idx) => { | |
| const slideNum = idx + 1; | |
| // 5a) Baseline | |
| const line = slide.insertLine( | |
| SlidesApp.LineCategory.STRAIGHT, | |
| MARGIN, lineY, | |
| W - MARGIN, lineY | |
| ); | |
| line.setTitle('AGENDA_LINE') | |
| .getLineFill().setSolidFill(baselineColor); | |
| line.setWeight(1); | |
| // 5b) Boundary dots | |
| for (let j = 0; j <= sections.length; j++) { | |
| const x = MARGIN + slotW * j; | |
| const dot = slide.insertShape( | |
| SlidesApp.ShapeType.ELLIPSE, | |
| x - DOT_D/2, lineY - DOT_D/2, | |
| DOT_D, DOT_D | |
| ); | |
| dot.setTitle(`AGENDA_BOUNDARY_DOT_${j}`) | |
| .getFill().setSolidFill(baselineColor); | |
| dot.getBorder().setWeight(0.1) | |
| .getLineFill().setSolidFill(baselineColor); | |
| } | |
| // 5c) Sections: bars, circles, labels | |
| sections.forEach((sec, i) => { | |
| const startX = MARGIN + slotW * i; | |
| const centerX = startX + slotW / 2; | |
| const totalSlides = sec.to - sec.from + 1; | |
| let progress = 0; | |
| if (slideNum >= sec.to) progress = 1; | |
| else if (slideNum >= sec.from) progress = (slideNum - sec.from + 1) / totalSlides; | |
| // Background bar | |
| const bgBar = slide.insertShape( | |
| SlidesApp.ShapeType.RECTANGLE, | |
| startX, barY, | |
| slotW, BAR_HEIGHT | |
| ); | |
| bgBar.setTitle(`AGENDA_BAR_BG_${i}`) | |
| .getFill().setSolidFill(inactiveBarBg); | |
| bgBar.getBorder().setWeight(0.1) | |
| .getLineFill().setSolidFill(inactiveBarBg); | |
| // Fill bar if needed | |
| const fillWidth = Math.min(slotW, slotW * progress); | |
| if (fillWidth > 0) { | |
| const fillBar = slide.insertShape( | |
| SlidesApp.ShapeType.RECTANGLE, | |
| startX, barY, | |
| fillWidth, BAR_HEIGHT | |
| ); | |
| fillBar.setTitle(`AGENDA_BAR_FILL_${i}`) | |
| .getFill().setSolidFill(activeBarFill); | |
| fillBar.getBorder().setWeight(0.1) | |
| .getLineFill().setSolidFill(activeBarFill); | |
| } | |
| // Circle marker | |
| const circle = slide.insertShape( | |
| SlidesApp.ShapeType.ELLIPSE, | |
| centerX - DIAMETER/2, circleY, | |
| DIAMETER, DIAMETER | |
| ); | |
| circle.setTitle(`AGENDA_CIRCLE_${i}`); | |
| if (slideNum >= sec.from && slideNum <= sec.to) { | |
| circle.getFill().setSolidFill(activeBarFill); | |
| circle.getBorder().setWeight(0.1) | |
| .getLineFill().setSolidFill(activeBarFill); | |
| } else { | |
| circle.getFill().setSolidFill(inactiveCircleFill); | |
| circle.getBorder().setWeight(1) | |
| .getLineFill().setSolidFill(inactiveBorder); | |
| } | |
| // Label (6pt, snug under the bar) | |
| const box = slide.insertTextBox( | |
| sec.name, | |
| startX, labelY, | |
| slotW, 10 | |
| ); | |
| box.setTitle(`AGENDA_LABEL_${i}`) | |
| .getFill().setTransparent(); | |
| const txt = box.getText().setText(sec.name); | |
| txt.getTextStyle() | |
| .setFontFamily('Roboto') | |
| .setFontSize(6) | |
| .setBold(slideNum >= sec.from && slideNum <= sec.to) | |
| .setForegroundColor( | |
| (slideNum >= sec.from && slideNum <= sec.to) | |
| ? activeLabelColor | |
| : inactiveLabelColor | |
| ); | |
| txt.getParagraphStyle() | |
| .setParagraphAlignment(SlidesApp.ParagraphAlignment.CENTER); | |
| }); | |
| }); | |
| SlidesApp.getUi().alert('✅ Agenda timeline added!'); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment

In Google Slides, click "Extensions" -> "Apps Script".
Then, copy and paste this code to add the progress bar.