Input fields embeded in pictures

I am trying to embed input fields in an image (drawing) and use them for some calculations.

I tried with this code but it doesn’t work.

I have very little knowledge of python and streamlit.

Does anyone have any idea if it can solve with python and streamlit.

Thanks in advance for your feedback.

import streamlit as st

# Custom CSS to style the image and position the input boxes
st.markdown("""
<style>
    .container {
        position: relative;
        width: 500px; /* Set the width of the container */
    }
    .image {
        width: 100%;
        height: auto;
    }
    .input-field-1, .input-field-2 {
        position: absolute;
        width: 100px;
    }
    .input-field-1 {
        top: 30%;  /* Adjust this to position the input field vertically */
        left: 30%; /* Adjust this to position the input field horizontally */
    }
    .input-field-2 {
        top: 50%;  /* Adjust this to position the input field vertically */
        left: 50%; /* Adjust this to position the input field horizontally */
    }
    .input-field button {
        position: absolute;
        top: 70%;
        left: 40%;
    }
</style>
""", unsafe_allow_html=True)

# Create a container with the image and input fields
st.markdown("""
<div class="container">
    <img src="upload://23MBX3qIGCcLv6bnrw3VgetomKG.png" class="image" alt="Image">
    <div class="input-field-1">
        <input id="field1" type="number" step="any">
    </div>
    <div class="input-field-2">
        <input id="field2" type="number" step="any">
    </div>
</div>
""", unsafe_allow_html=True)

# Create input fields using Streamlit's native components
field1 = st.number_input('First Number:', key='field1_input', label_visibility="collapsed")
field2 = st.number_input('Second Number:', key='field2_input', label_visibility="collapsed")

# Perform a calculation with the input values
if st.button("Calculate"):
    result = field1 + field2
    st.write(f"The result is: {result}")

As described, you’d need to build a custom component to build the unique frontend UI. This would require more than just Python code.

However, you can achieve something similar-ish:

You can use selections on charts to display an image with specific, selectable points. You can then conditionally display a widget based on point selection to collect information from the user.

Can you describe your use case in more detail?

It is an application in which a technical drawing with approx. 20 dimensions and tolerances. A tolerance calculation is carried out according to the changing dimensions and tolerances.
At the moment it is done in Excel. But in Excel, dimensions and tolerances have to be entered in a table and then only displayed on the drawing. The end user cannot enter or change them directly on the drawing.
The problem is that the end user must always know which information he enters in the table belongs to which drawing dimension. Even though in-put fields in the table and on the drawing have IDs or names, errors are always made because the end user has difficulty with the many entries he has to make.

I am therefore looking for a way to make these entries directly with an In-put field directly on the drawing/image without having to go via a table.

The input fields only need to be placed near the dimensions and tolerances. The drawing or image should only serve as a background.

Thanks that you serve time for my question

Hello,
I am still trying to create a customized streamlit component. I already got something working with typescript and class components but it doesn’t work properly with state. So I wanted to try with funktional component and jsx. Is it basically possible to create a customized streamlit component with jsx, hooks and customized function components?

I would be very grateful if I could get an answer here

I’ve asked a developer if they can comment further. My knowledge is limited to following the tutorial.

Hey @atila.celik ,

Do you have a repo where your custom component is located?

I suspect you are rendering an image and including input fields on the image, and then you would send the values of those input fields to the backend for update.

This can get quite complicated, so I wonder whether you considered a more straightforward columns with an image and data editor? It still has the cross-referencing needed, but it uses Streamlit built-in’s and would be easier to reconcile than the original excel solution.

import streamlit as st

# You may want to set a wide layout to make space for both the image and the date editor.
st.set_page_config(layout="wide")

col1, col2 = st.columns(2)
col1.image(PATH_TO_YOUR_IMAGE_WITH_LABELS)
col2.data_editor(DATAPOINTS)

Hey,
I’m actually a designer, not a programmer.
That’s why my code is probably not very clear.
But I’m just trying to learn.

I haven’t done a build yet but the code isn’t working properly either.
Error: It is rendered immediately after each single digit input. If I have to enter 3 digits in the input field, do it slowly and always wait until the next digit is rendered and so on.

The calcualtion with variables are just for test, with out any sens.

I need the inputs be saved for the case the User updates browser, because the UI will have upto 30 input fields, and if they are restort to defualt it will be funy, because need put in again. But did manage that it works with backup.json.

Thans for hint with “set a wide layout”

My English is not so good, but I hope I could make my point understandable

I hoppe you can help me to solve this problem.

Python Code:

import json
import os
import streamlit as st
import streamlit.components.v1 as components
import base64

st.set_page_config(layout="centered")
# st.set_page_config(layout="wide")

# Backup-Datei
BACKUP_FILE = "backup.json"

def load_state_from_file():
    if os.path.exists(BACKUP_FILE):
        with open(BACKUP_FILE, "r") as f:
            try:
                return json.load(f)
            except json.JSONDecodeError:
                return {}
    return {}

def save_state_to_file(state):
    with open(BACKUP_FILE, "w") as f:
        json.dump(state, f)

def get_image_base64(image_path):
    with open(image_path, "rb") as img_file:
        return base64.b64encode(img_file.read()).decode("utf-8")

def component_piston_variant(image_path, variable_values, key=None):
    base64_image = get_image_base64(image_path)
    _component_piston_variant = components.declare_component(
        "component_piston_variant",
        url="http://localhost:3001"  # Entwicklermodus
    )
    result = _component_piston_variant(
        base64Image=base64_image, variableValues=variable_values, key=key
    )
    return result

def main():
    st.title("Interaktive Tabelle mit Bildhintergrund")
    st.write("Änderungen werden gespeichert, wenn die Eingabe abgeschlossen ist.")

    state = load_state_from_file()

    # Standardwerte initialisieren
    default_values = {
        "variable_values1": 10,
        "variable_values2": 5,
        "variable_values3": 8,
        "variable_values4": 2,
        "variable_values5": 3,
        "variable_values6": 1,
    }

    # Überprüfung und Initialisierung
    for key, default_value in default_values.items():
        if key not in state or not isinstance(state[key], (int, float)):
            state[key] = default_value

    # Bildpfad
    image_path = r"C:\Users\104195\component_piston_variant\Stand_19_okt\sicherung\component_piston_variant\frontend\public\O-Ring-Hole.png"

    # Aktuelle Werte
    current_values = [
        state["variable_values1"],
        state["variable_values2"],
        state["variable_values3"],
        state["variable_values4"],
        state["variable_values5"],
        state["variable_values6"],
    ]

    # Benutzerinteraktion
    updated_values = component_piston_variant(image_path, current_values)

    if updated_values is not None:
        # Werte aktualisieren
        for i, key in enumerate(default_values.keys()):
            state[key] = updated_values[i]
        save_state_to_file(state)

    # Berechnungen
    st.write("### Ergebnisse der Berechnungen")
    sum1 = state["variable_values1"] + state["variable_values2"]
    product1 = state["variable_values1"] * state["variable_values2"]
    difference = state["variable_values1"] - state["variable_values6"]
    double_value = state["variable_values2"] * 2

    # Ergebnisse anzeigen
    st.write(f"**Summe der Variablenwerte (1 + 2):** {sum1}")
    st.write(f"**Produkt der Variablenwerte (1 * 2):** {product1}")
    st.write(f"**Unterschied (Variable 1 - Variable 6):** {difference}")
    st.write(f"**Doppelter Wert von Variable 2:** {double_value}")

if __name__ == "__main__":
    main()

React Component code:

import React from "react";
import { Streamlit, StreamlitComponentBase, withStreamlitConnection } from "streamlit-component-lib";
import "./ComponentPistonVariant.css"; // Separate CSS-Datei importieren

interface Props {
  args: {
    base64Image: string;
    variableValues: number[]; // Einzelne Werte für 6 Variablen
  };
  width: number; // Erwartete Eigenschaft
  disabled: boolean; // Erwartete Eigenschaft
}

interface State {
  d1_UpT: number;
  d1: number;
  d1_LoT: number;
  d2_UpT: number;
  d2: number;
  d2_LoT: number;
}

class ComponentPistonVariant extends StreamlitComponentBase<State> {
  public constructor(props: Props) {
    super(props);
    // Initialisieren der 6 Variablen mit den Werten von Streamlit
    this.state = {
      d1_UpT: this.props.args.variableValues[0],
      d1: this.props.args.variableValues[1],
      d1_LoT: this.props.args.variableValues[2],
      d2_UpT: this.props.args.variableValues[3],
      d2: this.props.args.variableValues[4],
      d2_LoT: this.props.args.variableValues[5],
    };
  }

  private onValueChange = (name: string, value: string): void => {
    // Wert aktualisieren
    const newValue = Number(value);
    this.setState(
      {
        [name]: newValue,
      } as Pick<State, keyof State>,
      () => {
        // Alle 6 Variablen an Streamlit senden
        Streamlit.setComponentValue([
          this.state.d1_UpT,
          this.state.d1,
          this.state.d1_LoT,
          this.state.d2_UpT,
          this.state.d2,
          this.state.d2_LoT,
        ]);
      }
    );
  };

  public render() {
    const { base64Image } = this.props.args;
    const imageUrl = `data:image/png;base64,${base64Image}`;

    return (
      <div className="background-container">
        <img src={imageUrl} alt="Background" className="background-image" />
        
        {/* Erste Tabelle */}
        <div className="table-container table-left">
          <table>
            <tbody>
              <tr>
                <td>d1-UpT</td>
                <td>
                  <input
                    type="number"
                    value={this.state.d1_UpT}
                    onChange={(e) => this.onValueChange("d1_UpT", e.target.value)}
                  />
                </td>
              </tr>
              <tr>
                <td>d1</td>
                <td>
                  <input
                    type="number"
                    value={this.state.d1}
                    onChange={(e) => this.onValueChange("d1", e.target.value)}
                  />
                </td>
              </tr>
              <tr>
                <td>d1-LoT</td>
                <td>
                  <input
                    type="number"
                    value={this.state.d1_LoT}
                    onChange={(e) => this.onValueChange("d1_LoT", e.target.value)}
                  />
                </td>
              </tr>
            </tbody>
          </table>
        </div>

        {/* Zweite Tabelle */}
        <div className="table-container table-right">
          <table>
            <tbody>
              <tr>
                <td>d2-UpT</td>
                <td>
                  <input
                    type="number"
                    value={this.state.d2_UpT}
                    onChange={(e) => this.onValueChange("d2_UpT", e.target.value)}
                  />
                </td>
              </tr>
              <tr>
                <td>d2</td>
                <td>
                  <input
                    type="number"
                    value={this.state.d2}
                    onChange={(e) => this.onValueChange("d2", e.target.value)}
                  />
                </td>
              </tr>
              <tr>
                <td>d2-LoT</td>
                <td>
                  <input
                    type="number"
                    value={this.state.d2_LoT}
                    onChange={(e) => this.onValueChange("d2_LoT", e.target.value)}
                  />
                </td>
              </tr>
            </tbody>
          </table>
        </div>
      </div>
    );
  }
}

export default withStreamlitConnection(ComponentPistonVariant);

CCS Kode:

.background-container {
    position: relative;
    width: 100%;
    height: 100%;
    display: flex;
    justify-content: center;
    align-items: center;
    overflow: hidden; /* Verhindert Überlauf */
}

.background-image {
    width: 100%;
    height: auto;
    object-fit: cover; /* Bild passt sich an den Container an */
}

.table-container {
    position: absolute;
}

.table-left {
    top: 39.8%; /* Originale Position */
    left: 43%; /* Originale Position */
    transform: scale(0.7); /* Tabellen um 30% kleiner machen */
}

.table-right {
    top: 5%; /* Originale Position */
    left: 37%; /* Originale Position */
    transform: scale(0.7); /* Tabellen um 30% kleiner machen */
}

table {
    border-collapse: collapse; /* Entfernt Leerraum zwischen Zellen */
    width: auto; /* Nur so breit wie nötig */
}

th, td {
    border: 1px solid #000; /* Dünne Rahmen */
    padding: 0px; /* Kein zusätzlicher Innenabstand */
    text-align: center; /* Zentrierter Text */
    min-width: 30px; /* Mindestbreite */
    font-size: 12px; /* Kleinere Schriftgröße */
    line-height: 1; /* Weniger vertikaler Platz */
}

input[type="text"], input[type="number"] {
    width: 60px; /* Platz für etwa 6 Zeichen */
    max-width: 100%; /* Passt sich an den Container an */
    border: 1px solid #ccc; /* Rahmen */
    border-radius: 2px; /* Ecken abgerundet */
    text-align: center; /* Zentrierter Text */
    padding: 1px; /* Minimales Padding */
    overflow: hidden; /* Verhindert Überlauf */
    text-overflow: ellipsis; /* Lässt überschüssigen Text mit "..." auslaufen */
    white-space: nowrap; /* Verhindert Zeilenumbrüche */
    -moz-appearance: textfield; /* Entfernt Pfeile in Firefox */
    -webkit-appearance: none; /* Entfernt Pfeile in Chrome/Safari */
    appearance: none; /* Entfernt Pfeile in modernen Browsern */
}

Looks like you are on a good path!

Error: It is rendered immediately after each single digit input. If I have to enter 3 digits in the input field, do it slowly and always wait until the next digit is rendered and so on.

Likely you will not want to set the ComponentValue on every change which will not rerun too many times. Two options:

  1. If you think one value changes and you want to rerun, you can try setting the Component Value on blur of one of the inputs (when it loses focus).
  2. If you expect multiple changes, you can add a button to send those values back.

Personally, I like (2), but it depends how you want the experience to look for users.

1 Like

(based on the comments in your code snippets I am switching to German. Please let us know if this works better for you :smiling_face: - though your English is great :+1:)

@atila.celik Bezüglich functional components in React, haben wir hier auch ein paar Beispiele im Template Repository. Zum Beispiel: component-template/examples/MaterialLogin/material_login/frontend/src/MaterialLogin.tsx at master · streamlit/component-template · GitHub
Functional Components sind moderner und in der Regel einfacher zu entwickeln. Wir müssen unbedingt mal unsere Beispielkomponente MyComponent im Repository updaten.

Hello,

I am still experiencing problems with creating this app.

Currently, I’m having trouble getting the values calculated by Python back to the frontend. I tested a template from GitHub, but I couldn’t implement it properly.

Unfortunately, I wasn’t able to set up a proper repository, so I placed the code in a zip file.

Link to zip file - this is just a test app to evaluate the customized hook only. My app will have a lot more variables to send to Streamlit and will also receive a lot back from Streamlit.

[AC-Yemi/react-customised-hook-to-send-and-get-data-from-streamli_public · GitHub]

I would greatly appreciate any advice on resolving this issue and implementing the customized hook correctly