Typescript not throwing error when passing wrong type props to child component

custom_types containing the wrong type of url as number instead of string

@types/index.ts

export type custom_types= {
  url: number;
}

Component.tsx

const Component: FC<{ tracks: custom_types[] }> = ({ tracks }) => {
   console.log(typeof tracks[0].url);      // string
   return (
        <div>
        </div>
   );
}

page.tsx

<Component tracks={userData.tracks} />

I am getting in console of this line console.log(typeof tracks[0].url);
as string though it should be of type number with error I was just checking and the above code is sample code

QML component’s property x is not updated in property animation

I am using a horizontal line (HighLighter) that comes under the text of the button pressed with property animation effect.

The problem is that, when I specify the default/initial property x of HighLighter component using expression (types.width/2-width/2), the animation starts from position x specified by the expression everytime, no matter what’s the current location of HighLighter component. Specifying immediate (constant) value of x yields no issue.

HighLigher blueprint

Item {
    id: root
    property var moveTo
    property alias move : move

    Rectangle
    {
        anchors.fill: parent
        width: parent.width
        height: parent.height
        color: "#55884b"
        PropertyAnimation
        {
            id: move
            target: root
            property: "x"
            duration: 100
            to: root.moveTo
        }
    }
}

main.qml file

HighLighter
    {
        id: highLighter
        x: types.width/2-width/2
        y: footerBar.y+ labelTypes.y + labelTypes.height*1.8
        z: 2
        width: root.highlighterWidth
        height: 2
        moveTo: highlighterMoveTo
    }
Rectangle
            {
                id: types
                property alias area: areaTypes
                property alias img: imgTypes
                property alias label: labelTypes

                Component.onCompleted:JS.currentObj=types

                width: footerBar.width/4
                height: (title.implicitHeight * 1.5) *1.5
                color: "#283142"
                Column
                {
                    anchors.top: parent.top
                    anchors.horizontalCenter: parent.horizontalCenter
                    anchors.topMargin: 10
                    spacing: 5

                    Image
                    {
                        id: imgTypes
                        anchors.horizontalCenter: parent.horizontalCenter
                        source: JS.updatedImgs[0]
                    }
                    Text {
                        id: labelTypes
                        anchors.horizontalCenter: parent.horizontalCenter
                        text: qsTr("Models")
                        color: "#55884b"
                        font.bold: true
                    }
                }
                MouseArea
                {
                    id: areaTypes
                    anchors.fill: parent
                    onPressed:
                    {
                        imgTypes.source="qrc:/logos/updated_metamorphose.png"
                        labelTypes.color="#55884b"
                    }
                    onReleased:
                    {
                        JS.returnImg(types, root, 0)
                    }
                }
            }

Java Script Code

var defaultImgs=["qrc:/logos/icons8-metamorphose-30.png", "qrc:/logos/icons8-circle-30.png",
                 "qrc:/logos/icons8-my-location-30.png", "qrc:/logos/icons8-details-30.png"]

var updatedImgs=["qrc:/logos/updated_metamorphose.png","qrc:/logos/updated_circle.png",
                 "qrc:/logos/updated_my-location.png","qrc:/logos/updated_details.png"]

var defaultColor="white"
var updatedColor="#55884b"

var tempImg, currentObj, i=0, currentIndex=0
var selected=["true", "false", "false", "false"]
var tempX

function returnImg(root, app, arr)
{
    if(root.area.containsMouse)
    {
        if(selected[arr]==="false")
        {
            tempImg=updatedImgs[arr]
            selected[arr]="true"
            root.img.source=updatedImgs[arr]
            root.label.color=updatedColor

            currentObj.img.source=defaultImgs[currentIndex]
            currentObj.label.color=defaultColor
            selected[currentIndex]="false"
            currentIndex=arr
            currentObj=root

            app.highlighterWidth=root.label.width
            tempX=root.x+(root.width)/2-(app.highLighter.width)/2
            app.highlighterMoveTo= tempX
            app.highLighter.move.start()
        }
        else
        {
            root.img.source=updatedImgs[arr]
            root.label.color=updatedColor
        }
    }
    else
    {
        if(selected[arr]==="false")
        {
            tempImg=defaultImgs[arr]
            root.img.source=tempImg
            root.label.color=defaultColor
        }
        else
        {
            root.img.source=updatedImgs[arr]
        }
    }
}

The table is cutting of on the bottom

The table on the bottom is cutting of while searching on mobile phone (it works fine on desktop)

you can access the web at https://cirrostrats.onrender.com/ (search 4399) to get the table

Attaching the screenshot as wellenter image description here

I tried editing the css for mobile and desktop site but the table is still cutting off on he bottom part

the code snippet

<div id="departure-container" class="table-container">
    <div class="accordion-button-container">
    <div class="accordion-content active" id="departure-content-{{ departure_ID }}">
        <table class="comparison-table">
            <thead>
                <tr>
                    <th>
                        <div class="header-button" onclick="toggleAccordion('departure-content-{{ departure_ID }}')">
                            &#9660; Departure
                        </div>
                    </th>
                </tr>
            </thead>
            <tbody>

                <td style="width: 100%; text-align: left;">
                    <span style="float: left; width: 33%;">{{ scheduled_departure_time }}</span>
                    <span style="float: left; width: 33%; text-align: center;">{{ departure_ID }}</span>
                    <span style="float: right; width: 33%; text-align: right;">{{ departure_gate }}</span>
                </td>
                

                <!-- Here got rid of for loops, makes it easier to work with design. -->
                <!-- TODO: Make it easier to replicate and reuse code without having to duplicate. extract this out to a new file for use one... -->
                    <!-- Maybe seperate out html alltogether in a different html file using >>> include 'partial_template1.html'  -->

                <!-- This is the first section has the zulu time and type of weather -->
                <tr>
                    <td>D-ATIS <span class="small-text"> {{ dep_datis_zt|safe }}</span> </td>
                </tr>
                <!-- This is the second section has the weather itself-->
                <tr>
                    <td style="text-align: left; font-family: Menlo, monospace;">{{dep_datis |safe}}</td>
                </tr>


                <tr>
                    <td>METAR <span class="small-text"> {{ dep_metar_zt |safe }}</span> </td>
                </tr>
                <tr>
                    <td style="text-align: left; font-family: Menlo, monospace;">{{dep_metar |safe}}</td>
                </tr>


                <tr>
                    <td>TAF <span class="small-text"> {{ dep_taf_zt |safe}}</span> </td>
                </tr>
                <tr>
                    <td id="dep_taf_cell" style="text-align: left; font-family: Menlo, monospace; white-space: wrap; overflow: scroll; text-overflow: ellipsis; max-height: none; height: auto;" title="{{dep_taf | safe}}">
                        {{dep_taf | safe}}
                    </td>
                </tr>        
            </tbody>
        </table>
    </div>
</div>

this is the part cutting of

                    <td id="dep_taf_cell" style="text-align: left; font-family: Menlo, monospace; white-space: wrap; overflow: scroll; text-overflow: ellipsis; max-height: none; height: auto;" title="{{dep_taf | safe}}">
                        {{dep_taf | safe}}

JS PathFinding without the ability to go up

I found several pathfinding implementations, but I didn’t find any where you can take into account that movement is possible only to the sides and down, but not up. Can you tell me where to watch this?

I tried to redo the options that I found, but I didn’t succeed at all

What could be JavaScript best practice to create a library? [closed]

Goal & Utility

I created a JavaScript code which aim to manipulate colours.

The main goal is to get CSS filter’s property based on a HEX colour, to apply it on DOM elements.
I use it to change elements’s colour to match images colours.

The second goal is to get dominant colour from image tag, as RGB object.

The question

I’m here because I want to know how to write a well-formed library, that could be used anywhere.
By now, I create libraries by just creating a constant object into a separate JS file, which works, but I think this is not the best way to do things.

So I’m looking for best practices to do that.

The code

Here is my code :

const kumo_filter = {
    red: 0,
    green: 0,
    blue: 0,
    gamma: 0.16666666666666666,
    alpha: 1,
    target: null,
    targetHSL: null,
    setBase: ( red, green, blue ) => {
        kumo_filter.red = kumo_filter.clamp( red );
        kumo_filter.green = kumo_filter.clamp( green );
        kumo_filter.blue = kumo_filter.clamp( blue );
        kumo_filter.target = { red: kumo_filter.red, green: kumo_filter.green, blue: kumo_filter.blue };
        kumo_filter.targetHSL = kumo_filter.hsl( kumo_filter.target.red, kumo_filter.target.green, kumo_filter.target.blue );
    },
    getBase: () => ( { red: kumo_filter.red ?? 0, green: kumo_filter.green ?? 0, blue: kumo_filter.blue ?? 0 } ),
    toString: () => `rgb(${ Math.round( kumo_filter.red ?? 0 ) }, ${ Math.round( kumo_filter.green ?? 0 ) }, ${ Math.round( kumo_filter.blue ?? 0 ) })`,
    hexToRGB: input => {
        input = input.replace( /^#?([a-fd])([a-fd])([a-fd])$/i, ( _, r, g, b ) => r + r + g + g + b + b );
        const result = /^#?([a-fd]{2})([a-fd]{2})([a-fd]{2})$/i.exec( input );
        return result ? {
            red: parseInt( result[ 1 ], 16 ),
            green: parseInt( result[ 2 ], 16 ),
            blue: parseInt( result[ 3 ], 16 )
        } : null;
    },
    clamp: value => {
        if( value > 255 ) return 255;
        else if( value < 0 ) return 0;
        return value;
    },
    solve: ( colorHex=null ) => {
        if( colorHex !== null && ( temp_color = kumo_filter.hexToRGB( colorHex ) ) !== null ) kumo_filter.setBase( temp_color.red, temp_color.green, temp_color.blue );
        return kumo_filter.css( kumo_filter.solveNarrow( kumo_filter.solveWide() ).values );
    },
    solveNarrow: wide => kumo_filter.spsa(
        wide.loss,
        [ 0.25 * ( wide.loss + 1 ), 0.25 * ( wide.loss + 1 ), ( wide.loss + 1 ), 0.25 * ( wide.loss + 1 ), 0.2 * ( wide.loss + 1 ), 0.2 * ( wide.loss + 1 ) ],
        2,
        wide.values,
        500
    ),
    solveWide: () => {
        let best = { loss: Infinity };
        for( let i = 0; best.loss > 25 && i < 3; i++ ) if( ( result = kumo_filter.spsa(
            5,
            [ 60, 180, 18000, 600, 1.2, 1.2 ],
            15,
            [ 50, 20, 3750, 50, 100, 100 ],
            1000
        ) ).loss < best.loss ) best = result;
        return best;
    },
    fix: ( value, id ) => {
        max = 100;
        if( id === 2 ) max = 7500;
        else if( id === 4 || id === 5 ) max = 200;
        if( id === 3 ) {
            if( value > max ) value %= max;
            else if( value < 0 ) value = max + value % max;
        } else if( value < 0 ) value = 0;
        else if( value > max ) value = max;
        return value;
    },
    spsa: ( acc, array, count, values, iterations ) => {
        const deltas = Array( 6 ), high = Array( 6 ), low = Array( 6 );
        let best = null, bestLoss = Infinity;
        for( let k = 0; k < iterations; k++ ) {
            const ck = count / Math.pow( k + 1, kumo_filter.gamma );
            for( let i = 0; i < 6; i++ ) {
                deltas[ i ] = Math.random() > 0.5 ? 1 : -1;
                high[ i ] = values[ i ] + ck * deltas[ i ];
                low[ i ] = values[ i ] - ck * deltas[ i ];
            }
            const diff = kumo_filter.loss( high ) - kumo_filter.loss( low );
            for( let i = 0; i < 6; i++ ) values[ i ] = kumo_filter.fix( values[ i ] - ( array[ i ] / Math.pow( acc + k + 1, kumo_filter.alpha ) ) * ( diff / ( 2 * ck ) * deltas[ i ] ), i );
            const loss = kumo_filter.loss( values );
            if( loss < bestLoss ) {
                best = values.slice( 0 );
                bestLoss = loss;
            }
        }
        return { values: best, loss: bestLoss };
    },
    loss: filters => {
        let color = { red: 0, green: 0, blue: 0 };
        color = kumo_filter.invert( color.red, color.green, color.blue, filters[ 0 ] / 100 );
        color = kumo_filter.sepia( color.red, color.green, color.blue, filters[ 1 ] / 100 );
        color = kumo_filter.saturate( color.red, color.green, color.blue, filters[ 2 ] / 100 );
        color = kumo_filter.hueRotate( color.red, color.green, color.blue, filters[ 3 ] * 3.6 );
        color = kumo_filter.brightness( color.red, color.green, color.blue, filters[ 4 ] / 100 );
        color = kumo_filter.contrast( color.red, color.green, color.blue, filters[ 5 ] / 100 );
        const colorHSL = kumo_filter.hsl( color.red, color.green, color.blue );
        return (
            Math.abs( color.red - kumo_filter.target.red ) +
            Math.abs( color.green - kumo_filter.target.green ) +
            Math.abs( color.blue - kumo_filter.target.blue ) +
            Math.abs( colorHSL.h - kumo_filter.targetHSL.h ) +
            Math.abs( colorHSL.s - kumo_filter.targetHSL.s ) +
            Math.abs( colorHSL.l - kumo_filter.targetHSL.l )
        );
    },
    invert: ( red, green, blue, value=1 ) => ( {
        red: kumo_filter.clamp( ( value + red / 255 * ( 1 - 2 * value ) ) * 255 ),
        green: kumo_filter.clamp( ( value + green / 255 * ( 1 - 2 * value ) ) * 255 ),
        blue: kumo_filter.clamp( ( value + blue / 255 * ( 1 - 2 * value ) ) * 255 )
    } ),
    linear: ( red, green, blue, slope=1, intercept=0 ) => ( {
        red: kumo_filter.clamp( red * slope + intercept * 255 ),
        green: kumo_filter.clamp( green * slope + intercept * 255 ),
        blue: kumo_filter.clamp( blue * slope + intercept * 255 )
    } ),
    sepia: ( red, green, blue, value=1 ) => kumo_filter.multiply( red, green, blue, [
        0.393 + 0.607 * ( 1 - value ),
        0.769 - 0.769 * ( 1 - value ),
        0.189 - 0.189 * ( 1 - value ),
        0.349 - 0.349 * ( 1 - value ),
        0.686 + 0.314 * ( 1 - value ),
        0.168 - 0.168 * ( 1 - value ),
        0.272 - 0.272 * ( 1 - value ),
        0.534 - 0.534 * ( 1 - value ),
        0.131 + 0.869 * ( 1 - value )
    ] ),
    saturate: ( red, green, blue, value=1 ) => kumo_filter.multiply( red, green, blue, [
        0.213 + 0.787 * value,
        0.715 - 0.715 * value,
        0.072 - 0.072 * value,
        0.213 - 0.213 * value,
        0.715 + 0.285 * value,
        0.072 - 0.072 * value,
        0.213 - 0.213 * value,
        0.715 - 0.715 * value,
        0.072 + 0.928 * value
    ] ),
    hueRotate: ( red, green, blue, angle=0 ) => {
        angle = angle / 180 * Math.PI;
        const sin = Math.sin( angle ), cos = Math.cos( angle );
        return kumo_filter.multiply( red, green, blue, [
            0.213 + cos * 0.787 - sin * 0.213,
            0.715 - cos * 0.715 - sin * 0.715,
            0.072 - cos * 0.072 + sin * 0.928,
            0.213 - cos * 0.213 + sin * 0.143,
            0.715 + cos * 0.285 + sin * 0.140,
            0.072 - cos * 0.072 - sin * 0.283,
            0.213 - cos * 0.213 - sin * 0.787,
            0.715 - cos * 0.715 + sin * 0.715,
            0.072 + cos * 0.928 + sin * 0.072
        ] );
    },
    multiply: ( red, green, blue, matrix ) => ( {
        red: kumo_filter.clamp( red * matrix[ 0 ] + green * matrix[ 1 ] + blue * matrix[ 2 ] ),
        green: kumo_filter.clamp( red * matrix[ 3 ] + green * matrix[ 4 ] + blue * matrix[ 5 ] ),
        blue: kumo_filter.clamp( red * matrix[ 6 ] + green * matrix[ 7 ] + blue * matrix[ 8 ] )
    } ),
    hsl: ( red, green, blue ) => {
        const r = red / 255, g = green / 255, b = blue / 255;
        const min = Math.min( r, g, b ), max = Math.max( r, g, b );
        let h, s, l = ( max + min ) / 2;
        if( max === min ) h = s = 0;
        else {
            const diff = max - min;
            s = l > 0.5 ? diff / ( 2 - max - min ) : diff / ( max + min );
            switch( max ) {
                case r:
                    h = ( g - b ) / diff + ( g < b ? 6 : 0 );
                    break;
                case g:
                    h = ( b - r ) / diff + 2;
                    break;
                case b:
                    h = ( r - g ) / diff + 4;
                    break;
            }
            h /= 6;
        }
        return { h: h * 100, s: s * 100, l: l * 100 };
    },
    brightness: ( red, green, blue, value=1 ) => kumo_filter.linear( red, green, blue, value ),
    contrast: ( red, green, blue, value=1 ) => kumo_filter.linear( red, green, blue, value, - ( 0.5 * value ) + 0.5 ),
    fmt: ( filters, id, multiplier=1 ) => Math.round( filters[ id ] * multiplier ),
    css: filters => `invert(${ kumo_filter.fmt( filters, 0 ) }%) sepia(${ kumo_filter.fmt( filters, 1 ) }%) saturate(${ kumo_filter.fmt( filters, 2 ) }%) hue-rotate(${ kumo_filter.fmt( filters, 3, 3.6 ) }deg) brightness(${ kumo_filter.fmt( filters, 4 ) }%) contrast(${ kumo_filter.fmt( filters, 5 ) }%)`,
    toHex: ( red, green, blue ) => "#" + [ red, green, blue ].map( x => x.toString( 16 ) ).join( '' ).toUpperCase(),
    getDominant: image_element => {
        let blockSize = 5,
            rgb = { r: 0, g: 0, b: 0 },
            canvas = document.createElement( 'canvas' ),
            context = canvas.getContext && canvas.getContext( '2d' ),
            data, width, height, length,
            i = -4,
            count = 0;
        if(
            image_element.tagName !== 'IMG' ||
            ! context
        ) return rgb;
        // if( image_element.crossOrigin !== 'anonymous' ) {
        //     console.error( "Can't get image data due to cross-origin." );
        //     return rgb;
        // }
        height = canvas.height = image_element.naturalHeight || image_element.offsetHeight || image_element.height;
        width = canvas.width = image_element.naturalWidth || image_element.offsetWidth || image_element.width;
        context.drawImage( image_element, 0, 0 );
        try {
            data = context.getImageData( 0, 0, width, height );
        } catch( e ) {
            return rgb;
        }
        length = data.data.length;
        while( ( i += blockSize * 4 ) < length ) {
            ++count;
            rgb.r += data.data[ i ];
            rgb.g += data.data[ i + 1 ];
            rgb.b += data.data[ i + 2 ];
        }
        rgb.r = ~~( rgb.r / count );
        rgb.g = ~~( rgb.g / count );
        rgb.b = ~~( rgb.b / count );
        return rgb;
    }
};

Usage example

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Example</title>
    <script src="./color.js"></script>
</head>
<body>
    <img id="testImage" src="https://i.stack.imgur.com/G2RnM.jpg" alt="Test Image">
    <svg xmlns="http://www.w3.org/2000/svg" id="changeColor" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M17 3.34a10 10 0 1 1 -14.995 8.984l-.005 -.324l.005 -.324a10 10 0 0 1 14.995 -8.336zm-1.051 6.844a1 1 0 0 0 -1.152 -.663l-.113 .03l-2.684 .895l-2.684 -.895l-.113 -.03a1 1 0 0 0 -.628 1.884l.109 .044l2.316 .771v.976l-1.832 2.75l-.06 .1a1 1 0 0 0 .237 1.21l.1 .076l.101 .06a1 1 0 0 0 1.21 -.237l.076 -.1l1.168 -1.752l1.168 1.752l.07 .093a1 1 0 0 0 1.653 -1.102l-.059 -.1l-1.832 -2.75v-.977l2.316 -.771l.109 -.044a1 1 0 0 0 .524 -1.221zm-3.949 -4.184a1.5 1.5 0 1 0 0 3a1.5 1.5 0 0 0 0 -3z" stroke-width="0" fill="currentColor" /></svg>
    <script>
        const new_color = "#8f32a8";
        document.addEventListener( 'DOMContentLoaded', () => {
            document.querySelector( '#changeColor' ).style.filter = kumo_filter.solve( new_color );
            document.querySelector( '#testImage' ).onload = function() {
                this.style.boxShadow = "0 0 10px 5px #" + Object.values( kumo_filter.getDominant( this ) ).join( '' )
            };
        } );
    </script>
</body>
</html>

Explanations :

  • kumo_filter.solve() takes HEX string as argument, and return a CSS filter like the following : invert(55%) sepia(99%) saturate(1995%) hue-rotate(128deg) brightness(85%) contrast(77%)
  • kumo_filter.getDominant() takes a DOM IMG tag as argument. It should return RGB object (like {r:0,g:0,b:0}). It cannot work in the example above, because of CORS policies.

I tried to figure out what could be the best practices by reading others libraries.
But it seems like there is multiple possibilities, so I’m not sure which one is actually the best.

After clone image upload and date picker is not working

image upload and date picker not working after clone

This is the section i want to duplicate when i click add more button

 <div class="multi-area">
                <div class="row duplicate-row">
                  <div class="col-6">
                    <div class="input">
                      <input
                        type="text"
                        placeholder="Enter Your Course Name"
                        />
                    </div>
                  </div>
                  <div class="col-6">
                    <div class="input">
                      <input
                        type="text"
                        placeholder="Enter Your Institute Name"
                        />
                    </div>
                  </div>
                 <div class="col-6">
                  <h6 class="headings">Course Completion Date</h6>
                  <!-- Date Picker Input -->
                  <div class="form-group">
                    <div class="datepicker date input-group">
                      <input
                        type="text"
                        placeholder=""
                        class="form-control"
                        id="reservationDate"
                        />
                      <div class="input-group-append">
                        <span class="input-group-text px-4 gap-3"><i class="fa-solid
                            fa-calendar"></i>Pick Date</span>
                      </div>
                    </div>
                  </div>
                 </div>
                <div class="col-6">
                    <!-- DEnd ate Picker Input -->
                    <h6 class="headings">
                     Education Certificate
                      <span>
                         (Recommended file Dimensions: 200 X 200
                        Pixels)</span>
                    </h6>
                    <div class="ux-a-file-upload">
                      <input
                        type="file"
                        name="file name"
                        class="ux-js-a-file-upload__input-file
                        ux-a-file-upload__input-file"
                        />
                      <div class="input">
                        <input
                          type="text"
                          class="ux-js-a-file-upload__input-text
                          ux-a-file-upload__input-text"
                          />
                      </div>
                      <button class="ux-a-file-button rounded-end">
                        <i
                          class="fa-solid fa-cloud-arrow-up me-2"
                          style="color: #0c061e"></i>chose file
                      </button>
                    </div>
                </div>
                <div class="col-12 text-end">
                  <span class="removebtn">
                    <i class="fa-solid fa-trash-can"></i>
                  </span>
                </div>
                </div>
              </div>

This is the js


// qualification clone
$(document).ready(function () {
  $('#addbtn').click(function () {
  $('.duplicate-row:last-child').clone().appendTo('.multi-area');
  });
  $(document).on('click' , '.removebtn' ,function(){
    if($('.duplicate-row').length > 1){
      $(this).parents('.duplicate-row').remove();
    }
  });

});

after clone the date picker and image uploads are not working. but if i manually duplicate them they are working.what could be the solution for this,thanks in advance

after clone the date picker and image uploads are not working. but if i manually duplicate them they are working.what could be the solution for this,thanks in advance

How can I build a static website that also contains a dynamic web app for one of the pages?

I want to build a resume builder website. Initially, I was thinking that I’d want to have a few static, informative pages (about, contact, etc). Then I was thinking of having a separate web app page for the resume builder where I would use something like react. The resume builder would allow users to input general info about themselves and I would then use it to generate a resume. I want it to be interactive and dynamic.

I’m open to any and all suggestions regarding which tools/framework(s) to use. I would consider using something like react, but I know SPAs don’t play great with SEO out of the box. Let me know what you guys think the best approach would be. I’m a backend dev so I’m out of my depth here.

It almost seems to me like I need a multi-page static site with 1 app page, but maybe I’m looking at this the wrong way. At the end of the day, I just want it to look good and perform well for its users.

I’ve done quite a bit of digging into this but can’t seem to come up with a good approach given my criteria.

I’m building a recipe searching website and whenever I click on a search result the page reloads

I’m currently going through Jonas Schmedtmann’s course and ran into a problem. When I search something in a search bar (eg: pizza) it returns the results but when I click on one of the results, it reloads the page instead of displaying it. There is no error in the console either.

Here is the code:

model.js

import { async } from "regenerator-runtime";
import { API_URL } from "./config.js";
import { getJSON } from "./helpers.js";

export const state = {
    recipe: {},
    search: {
      query: '',
      results: [],
    }
};

export const loadRecipe = async function (id) {
   try {
    
    const data = await getJSON(`${API_URL}${id}`);
      
    
      const {recipe} = data.data;
       state.recipe = {
        id: recipe.id,
        title: recipe.title,
        publisher: recipe.publisher,
        sourceUrl: recipe.source_url,
        image: recipe.image_url,
        servings: recipe.servings,
        cookingTime: recipe.cooking_time,
        ingredients: recipe.ingredients,
      }
    console.log(state.recipe);
   
} catch(err) {
    console.log(`${err}`);
    throw err;
}


    
}

export const loadSearchResults = async function(query) {
  try {
    const data = await getJSON(`${API_URL}?search=${query}`);
    console.log(data);

    state.search.results = data.data.recipes.map(rec => {
      return {
        id: rec.id,
        title: rec.title,
        publisher: rec.publisher,
        image: rec.image_url,
        
      }
    })
  } catch(err) {
    console.log(`${err}`);
    throw err;
  }
}


controller.js

import * as model from './model.js';
import recipeView from './views/recipeView.js';
import searchView from './views/searchView.js';
import resultView from './views/resultView.js';


import 'core-js/stable';
import 'regenerator-runtime/runtime'
import { async } from 'regenerator-runtime';

const controlRecipes = async function() {
  try {
  const id = window.location.hash.slice(1);

  if(!id) return;

  recipeView.renderSpinner();

    //Loading Recipes
   await model.loadRecipe(id);
  

  //Rendering 
  recipeView.render(model.state.recipe);

  
  } catch(err) {
    recipeView.renderError();
  }
}

const controlSearchResults = async function() {
  try {
   resultView.renderSpinner();

    //Get search query
   const query = searchView.getQuery();
   if(!query) return;
   
   //Load search results
   await model.loadSearchResults(query);

   //Render results
   
   resultView.render(model.state.search.results);
  } catch(err) {
     console.log(err);
  }
}

controlSearchResults();


const innit = function() {
  recipeView.addHandlerRender(controlRecipes);
  searchView.addHandlerSearch(controlSearchResults);
}
 
innit()








recipeView.js

import VIew from './View';

import icons from  '../../img/icons.svg';
import {Fraction} from 'fractional';

class RecipeView extends VIew {
    _parentElement = document.querySelector('.recipe');
    
    _errorMessage = `We could not find that recipe. Please try another one.`;
    _message = '';

    addHandlerRender (handler) {
      window.addEventListener('hashchange',handler);
      window.addEventListener('load',handler);
    }
    
    _generateMarkup() {
        return  `
  <figure class="recipe__fig">
          <img src="${this._data.image}" alt="Tomato" class="recipe__img" />
          <h1 class="recipe__title">
            <span>${this._data.title}</span>
          </h1>
        </figure>

        <div class="recipe__details">
          <div class="recipe__info">
            <svg class="recipe__info-icon">
              <use href="${icons}#icon-clock"></use>
            </svg>
            <span class="recipe__info-data recipe__info-data--minutes">${this._data.cookingTime}</span>
            <span class="recipe__info-text">minutes</span>
          </div>
          <div class="recipe__info">
            <svg class="recipe__info-icon">
              <use href="${icons}#icon-users"></use>
            </svg>
            <span class="recipe__info-data recipe__info-data--people">${this._data.servings}</span>
            <span class="recipe__info-text">servings</span>

            <div class="recipe__info-buttons">
              <button class="btn--tiny btn--increase-servings">
                <svg>
                  <use href="${icons}#icon-minus-circle"></use>
                </svg>
              </button>
              <button class="btn--tiny btn--increase-servings">
                <svg>
                  <use href="${icons}#icon-plus-circle"></use>
                </svg>
              </button>
            </div>
          </div>

          <div class="recipe__user-generated">
            <svg>
              <use href="${icons}#icon-user"></use>
            </svg>
          </div>
          <button class="btn--round">
            <svg class="">
              <use href="${icons}#icon-bookmark-fill"></use>
            </svg>
          </button>
        </div>

        <div class="recipe__ingredients">
          <h2 class="heading--2">Recipe ingredients</h2>
          <ul class="recipe__ingredient-list">
               ${this._data.ingredients.map(this._generateMarkUpIngredient).join('')}
                
                

           
              </div>
            </li>
          </ul>
        </div>

        <div class="recipe__directions">
          <h2 class="heading--2">How to cook it</h2>
          <p class="recipe__directions-text">
            This recipe was carefully designed and tested by
            <span class="recipe__publisher">${this._data.publisher}</span>. Please check out
            directions at their website.
          </p>
          <a
            class="btn--small recipe__btn"
            href="${this._data.sourceUrl}"
            target="_blank"
          >
            <span>Directions</span>
            <svg class="search__icon">
              <use href="src/img/icons.svg#icon-arrow-right"></use>
            </svg>
          </a>
        </div>
  `;
  
    }

   _generateMarkUpIngredient(ing) {

    return `
    <li class="recipe__ingredient">
  <svg class="recipe__icon">
    <use href="src/img/icons.svg#icon-check"></use>
  </svg>
  <div class="recipe__quantity">${ing.quantity ? new Fraction(ing.quantity).toString() : ""}</div>
  <div class="recipe__description">
    <span class="recipe__unit">${ing.unit}</span>
    ${ing.description}
  </div>
</li> `;
}
}
export default new RecipeView();

View.js

import icons from  '../../img/icons.svg';

export default class VIew {
    _data;
    render(data) {
        this._data = data;
        const markup = this._generateMarkup();
        this._clear();
        this._parentElement.insertAdjacentHTML('afterbegin', markup)
    }

    _clear() {
        this._parentElement.innerHTML = '';
    }

    renderSpinner() {
        const markup = `
        <div class="spinner">
                <svg>
                  <use href="${icons}#icon-loader"></use>
                </svg>
              </div>
        `
       this._parentElement.innerHTML = '';
       this._parentElement.insertAdjacentHTML('afterbegin', markup)
      }

    
    
    renderError(message = this._errorMessage) {
      const markup = `
      <div class="error">
      <div>
        <svg>
          <use href="${icons}#icon-alert-triangle"></use>
        </svg>
      </div>
      <p>${message}</p>
    </div>
      `

     this._clear();
     this._parentElement.insertAdjacentHTML('afterbegin',markup) 
    }

    renderMessage(message = this._message) {
      const markup = `
      <div class="message">
      <div>
        <svg>
          <use href="${icons}#icon-smile"></use>
        </svg>
      </div>
      <p>${message}</p>
    </div>
      `

     this._clear();
     this._parentElement.insertAdjacentHTML('afterbegin',markup) 
    }

}

resultView.js

import icons from  '../../img/icons.svg';
import View from "./View";

class ResultView extends View {
 _parentElement = document.querySelector('.results');

 _generateMarkup() {
   return this._data.map(this._generateMarkupPreview).join('');

    
 }

 _generateMarkupPreview(result) {
    return `
    <li class="preview">
            <a class="preview__link " href="${result.id}">
              <figure class="preview__fig">
                <img src="${result.image}" alt="${result.title}" />
              </figure>
              <div class="preview__data">
                <h4 class="preview__title">${result.title}</h4>
                <p class="preview__publisher">${result.publisher}</p>
                
              </div>
            </a>
          </li>
    `
 }
}

export default new ResultView();

searchView.js

class SearchView {
  _parentEL = document.querySelector('.search');

  getQuery() {
   const query = this._parentEL.querySelector('.search__field').value;
   this._clearInput();
   return query;
  }

  _clearInput() {
    this._parentEL.querySelector('.search__field').value = '';
  }

  addHandlerSearch(handler) {
    this._parentEL.addEventListener('submit', function(e) {
        e.preventDefault();

        handler();
    })
  }
}

export default new SearchView();

helpers.js

import { TIMEOUT_SECS } from "./config.js";

const timeout = function (s) {
    return new Promise(function (_, reject) {
      setTimeout(function () {
        reject(new Error(`Request took too long! Timeout after ${s} second`));
      }, s * 1000);
    });
  };

export const getJSON = async function(url) {
    try {
        const res = await Promise.race([fetch(url), timeout(TIMEOUT_SECS)]);
       
        const data = await res.json();  
      
    
        if(!res.ok) throw new Error(`${data.message} (${res.status})`) ;
        return data;
    } catch(err) {
       throw err;
    }
}


config.js

export const API_URL = 'https://forkify-api.herokuapp.com/api/v2/recipes/';
export const TIMEOUT_SECS = 10;

Is there a way to overcome the 404 error not found due to CORS when trying to update or create an excel file with Graph API?

i’m quite new to the programming world so sorry for my language and in a dire need of help.
I’m trying to use the Microsoft Graph API to update or create an excel file on OneDrive with a full stack app, a node express server written in javascript as backend and a Vue.js component as a frontend: the code works perfectly when used in local without any api but when I add the HTTP request to the API everything crashed and I receive a 404 error not found even if the file exists. Looking around, I’ve found that the main issue may be the CORS policy and I’ve tried a lot of things to overcome this issue but without luck.

This is my generateExcel function

    async generateExcel() {
  if (!this.inputFileName) {
    alert('Per favore inserisci il nome del file.');
    return;
  }

  if (!this.validateInput()) {
    alert('Per favore inserisci solo numeri nei campi dati.');
    return;
  }

  const data = [this.ICCID, this.IMSI, this.MSISDN, this.IMEI, this.COMPANY];
  const fileName = this.inputFileName.includes('.xlsx') ? this.inputFileName : `${this.inputFileName}.xlsx`;

  // parte di codice che effettua la richiesta API per caricare o aggiornare il file su OneDrive, 
  try {
    const response = await fetch(`https://graph.microsoft.com/v1.0/me/drive/root:/Test/${fileName}:/content`, {
      method: 'PUT',
      headers: {
        'Content-Type': 'application/octet-stream',
        'Authorization': 'Bearer ' + this.accessToken, 
      },
      body: data,
    });

    if (!response.ok) {
      throw new Error('Network response was not ok');
    }

    alert("File aggiornato con successo.");
  } catch (error) {
    console.error('Errore durante l'aggiornamento del file:', error);
    alert("Errore durante l'aggiornamento del file.");
  }
    },

Do you have some tips to share?
Have a great day and keep going!

Charlie

Tried with fetch, axios, app.use(cors({
origin: ‘http://localhost:8080’
}));
Also changed browser and used extensions for Cors

AJAX POST misformatting base64 string

I am having trouble getting an AJAX POST to correctly upload/return my base64 string to/from my SQL database. When I return the string from my database via AJAX, I receive exactly the same base64 string, but with random line breaks. Sadly, these line breaks make the string unusable.

At the top of the console you can see the incorrectly formatted base64, while the bottom is correctly formatted.

Photo Here

When I view the base64 string in my SQL database, it is incorrectly formatted, which leads me to believe the initial AJAX upload is where the issue occurs. I have my SQL datatype set to MEDIUMTEXT, so that could be an issue, but I saw no difference when I changed the datatype.

//Getting the base64 string
let canvas = picCheckIn.getContext("2d")
let dataURL = canvas.canvas.img.src
//Hard-coding the base64 string to the image's source
imgPreviousIn.src="data:image/jpeg;base64,/9j/4A...<Rest of base64 string>"

//Assigning the AJAX base64 string to the image's source
let req = Ajax(<POST query here...>)
let results = JSON.parse(req.responseText)
imgPreviousIn.src=results[0][0]

The error code I get:

GET data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/4gKgSUNDX1… net::ERR_INVALID_URL

How do I make video play in the background using HTML CSS JS

I want to play each movie trailer while the user scrolls in the background. Like as the user scrolls to a new movie after some time the video starts playing. A division between the movies, like from perks of being a wallflower to top gun then the top gun video clip starts playing inside the div behind the content and content is very visible

HTML

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>TV Shows</title>
    <link rel = "stylesheet" href = "style.css">
</head>
<body>
    <header>
        <h1>My Favorite TV Shows</h1>
    <nav class = "main">
            <a ref = "#">Login</a>
            <a ref = "">Contact Me</a>
            <a ref = "">Feeling lucky!</a>
            <div class="Theme">
                <button id = "theme-toggle">Theme</button>
            </div>
        </nav>
    </header>

    <div class="Perks">
        <video autoplay muted loop id = "perks-bg">
            <source src = "Top Gun Maverick  - Motorcycle Kawasaki Ninja H2.mp4" type="video/mp4">
        </video>
        <img src="https://m.media-amazon.com/images/M/MV5BZThjMmQ5YjktMTUyMC00MjljLWJmMTAtOWIzNDIzY2VhNzQ0XkEyXkFqcGdeQXVyMTAyNjg4NjE0._V1_.jpg" alt="Perks">
        <h4>The Perks of Being a Wallflower</h4>
    </div>
    <div class="Top">
        <img src = "https://m.media-amazon.com/images/M/MV5BZjQxYTA3ODItNzgxMy00N2Y2LWJlZGMtMTRlM2JkZjI1ZDhhXkEyXkFqcGdeQXVyNDk3NzU2MTQ@._V1_FMjpg_UX1000_.jpg" alt="Top">
        <h4>Top Gun</h4>
    </div>
    <div class="Kung">
        <img src="https://m.media-amazon.com/images/M/MV5BODJkZTZhMWItMDI3Yy00ZWZlLTk4NjQtOTI1ZjU5NjBjZTVjXkEyXkFqcGdeQXVyODE5NzE3OTE@._V1_.jpg" alt="Kung">
        <h4>Kung Fu Panda</h4>
    </div>
    <div class="Conjuring">
        <img src="https://m.media-amazon.com/images/M/MV5BMTM3NjA1NDMyMV5BMl5BanBnXkFtZTcwMDQzNDMzOQ@@._V1_.jpg" alt="Conjuring">
        <h4 >The Conjuring</h4>
    </div>
    <div class="Grown">
        <img src="https://m.media-amazon.com/images/M/MV5BMjA0ODYwNzU5Nl5BMl5BanBnXkFtZTcwNTI1MTgxMw@@._V1_.jpg" alt="Grown">
        <h4>The Grown Ups</h4>
    </div>
    <script src = "script.js"></script>
</body>
</html>

CSS

body, html {

    margin: 0;
    padding: 0;
    box-sizing: border-box;
    overflow-x: hidden;

}

.Perks,
.Top,
.Kung,
.Conjuring,
.Grown {
    color: darkkhaki;
    font-size: 20px;
    
    font-style: oblique;
    padding-bottom: 400px;
    padding-top: 300px;
    padding-left: 150px;
    margin: auto;
    border: 1px solid #ccc;
    overflow-y: auto;
    position: relative;

}

img {
    margin: 0 auto; 
    width: auto;
    height: 700px;
}

header{
    background-color: #c7b198;
    border: solid;
    display:flex;
    justify-content: space-between;
    align-items: center;
    height: 100px;
    padding: 0 5%;
}

header nav {
    display: flex;
    gap: 30px;
}

header nav a {
    text-decoration: none;
    font-size: 17px;
    font-weight: 600;
    padding: 8px 16px;
    border-radius: 99px;

}

header nav a.onto {
        background: gray
}

header nav a:hover {
    background: gray;
}

header nav a:hover > a:not(:hover) {
    background: none;
}

#theme-toggle{
    background-color: black;
    color: aliceblue;
    background-color: #333;
    color: #fff

}
.dark-theme{
    background-color: #000000;
    color: #fff;
}

I tried doin the basics but it did not work because I am starting out

Intercepting image.src assignment

I need to remap, from a single-page application client’s side, the request of several resources.

I can intercept the requests of resources that are loaded through fetch in this way

const {fetch: originalFetch} = window;

window.fetch = async (...args) => 
{
 let [resource, config ] = args;

 resource = resource.replace("www.somewhere", "www.elsewhere");
 const response = await originalFetch(resource, config);
 
 return response;
};

However, there’s a billion pieces of code where images (and video, and audio files for that matter) are loaded by simple assignment

var image = new Image();

image.onload = 
function()
{
 // something happens here
}

image.src = "www.somewhere.com/hello.jpg";

Is there any way to intercept assignments and operate on src before the actual request is processed?

Why does `display: unset` on a div make it `inline`?

According to MDN:

The unset CSS keyword resets a property to its inherited value if the property naturally inherits from its parent, and to its initial value if not. In other words, it behaves like the inherit keyword in the first case, when the property is an inherited property, and like the initial keyword in the second case, when the property is a non-inherited property.

So, since display is NOT an inherited value, I’m expecting the display: unset to make a div be block which is its “initial value”.

But doing a quick test it gives me the value of inline:

let output = document.querySelector('#output');
let box = document.querySelector('#box');
let btn = document.querySelector('#btn');

btn.addEventListener('click', () => {
  let computedDisplay = box.computedStyleMap().get('display').value;
  output.innerText = computedDisplay;
});
#box {
  display: unset;
}
<div>
  <div id="output"></div>
  <div id="box">box</div>
  <button id="btn">Get</button>
</div>

What am I understanding wrong here?

Convert the lineal scale in a logarithmic scale

I have a lineal graph in HTML, and want it to be in a logarithmic scale. How do I achieve this?

  // Crappy code ahead
  function drawFigure() {
    let svg = document.getElementById('chart');

    let dataAreaBounds = {
      'x0': 70,
      'y0': 20,
      'x1': 615,
      'y1': 345,
    };

    let xScale = new Scale([2014, 2024], [dataAreaBounds.x0, dataAreaBounds.x1]);
    let yScale = new Scale([1, 2e25], [dataAreaBounds.y1, dataAreaBounds.y0]); // TODO: convert to log scale

    drawXAxis(svg, dataAreaBounds, xScale);
    drawYAxis(svg, dataAreaBounds, yScale);

    // Line of best fit
    // Fitted by David
    let linePoints = [];
    for (let x = 2014; x <= 2023.5; x += 0.01) {
      linePoints.push({x: x, y: 4e19 * 10**(0.6 * (x - 2014))});
    }
    drawLine(svg, linePoints, xScale, yScale);

    // Plot some random points for visual flair
    let scatterPoints = [
      {x: 2014.6513328592291, y: 6.522445718826063},
      {x: 2014.5004919770445, y: 28.54161083691497},
      {x: 2014.5017707561346, y: 356.90171758037377},
      {x: 2014.589177913871,  y: 2826.562425016834},
      {x: 2014.7918112212005, y: 4351.922830978623},
      {x: 2014.8699072253535, y: 10.766908479473404},
      {x: 2017.2039539705256, y: 11.080197214851651},
      {x: 2017.0718734376444, y: 68.97719500516978},
      {x: 2017.0731524940668, y: 862.5387823824755},
      {x: 2017.160559443804,  y: 6831.030422150273},
      {x: 2017.3631927511335, y: 10517.403343855975},
      {x: 2017.5345317466165, y: 17.13725136492356},
      {x: 2017.4195993612288, y: 242.9046774333274},
      {x: 2017.5986660817082, y: 101.5812129127676},
      {x: 2015.2871159061615, y: 17.42330939154803},
      {x: 2015.2447848793565, y: 143.53355538680938},
      {x: 2015.2659764619923, y: 1395.9733917992628},
      {x: 2015.4467697145092, y: 6500.951869303485},
      {x: 2015.6839478758716, y: 4064.686697959286},
      {x: 2015.7278065874775, y: 493.70863468584554},
      {x: 2015.7602009377672, y: 71.87618581674568},
      {x: 2015.677664774662,  y: 14.251249108908684},
      {x: 2015.4886129831868, y: 11.214784296707869},
      {x: 2018.9655637562169, y: 66.63590587475387},
      {x: 2018.9232324520797, y: 548.9478647072525},
      {x: 2018.9444241040485, y: 5338.900828345252},
      {x: 2019.1252177032309, y: 24862.893178302238},
      {x: 2019.3623964192577, y: 15545.406536639537},
      {x: 2019.4062544375329, y: 1888.19400256063},
      {x: 2021.1713744870801, y: 29041.70395982831},
      {x: 2020.870351109516,  y: 29041.70395982831},
      {x: 2020.7608866764676, y: 8711.749084043708},
      {x: 2020.7335216082015, y: 1530.3170380118645},
      {x: 2020.8977168711126, y: 120.46076715704281},
      {x: 2021.1303258446849, y: 92.1811596521025},
      {x: 2021.2671553459993, y: 14.164949214201702},
      {x: 2021.1303258446849, y: 3.717044282516151},
      {x: 2020.8566675353868, y: 1.9040841924455403},
      {x: 2020.6514243234114, y: 4.249124573767502},
      {x: 2019.434267076646,  y: 208.18755101605092},
      {x: 2019.2010347988003, y: 137.7042528950963},
      {x: 2019.4473786529713, y: 32.871438831662445},
      {x: 2019.4374009312803, y: 5.166067521864775},
      {x: 2019.9985148302355, y: 23338.816801559216},
      {x: 2020.042372848511,  y: 2834.8061408171834},
      {x: 2019.7413751241816, y: 22447.361446939456},
      {x: 2020.309350916791,  y: 25340.63579762137},
      {x: 2020.070385487624,  y: 312.5573679360577},
      {x: 2020.0834970639492, y: 49.35107791401531},
      {x: 2020.073520035589,  y: 7.755951109149252},
      {x: 2018.9739918838573, y: 6.137689006065227},
      {x: 2017.9805433248366, y: 66.89751619827624},
      {x: 2017.9382120206997, y: 551.1030155466955},
      {x: 2017.9594036033352, y: 5359.901141571594},
      {x: 2018.140196855852,  y: 24960.690189568184},
      {x: 2018.3773750172145, y: 15606.543076328118},
      {x: 2018.4212340061526, y: 1895.6198390805791},
      {x: 2018.2199013871625, y: 507.117658088768},
      {x: 2018.3680667756198, y: 83.10383311818435},
      {x: 2018.5245728627199, y: 15.975305341057217},
      {x: 2017.952522781754,  y: 6.834180746628639},
      {x: 2016.1348910458237, y: 62.979427628338634},
      {x: 2016.1550304991656, y: 582.5617780646912},
      {x: 2016.1495520082,    y: 5657.997276846421},
      {x: 2016.414606846531,  y: 487.83661543618985},
      {x: 2016.6998652019577, y: 911.4910375141101},
      {x: 2016.7026576605692, y: 13.275527977085018},
      {x: 2016.3069282946258, y: 2133.154823480869},
      {x: 2016.7073770931158, y: 124.65941494960086},
      {x: 2016.5346591322607, y: 77.21333980683943},
      {x: 2016.7070877662268, y: 5045.62392127498},
      {x: 2016.1621842850323, y: 8.503394534564555},
      {x: 2017.3759123182972, y: 8.20724982232243},
      {x: 2021.8144712712647, y: 21.160364864629127},
      {x: 2021.9239350109824, y: 2286.057703311427},
      {x: 2021.93761719845,   y: 17006.532297699443},
      {x: 2021.9649829600467, y: 84690.96884160757},
      {x: 2021.9786665341758, y: 188994.40400348319},
      {x: 2022.0060322957725, y: 630036.0303498339},
      {x: 2022.0197151765708, y: 1607233.438225285},
      {x: 2022.0197151765708, y: 4686989.345604965},
      {x: 2022.0197151765708, y: 11956587.451004488},
      {x: 2022.0197151765708, y: 30501473.300898027},
      {x: 2022.0197151765708, y: 59542747.69213289},
      {x: 2022.0197151765708, y: 88947443.27985303},
      {x: 2022.0197151765708, y: 101680402.97089978},
      {x: 2022.0197151765708, y: 101680402.97089978},
      {x: 2021.9239350109824, y: 20418031.285764568},
      {x: 2021.8555206069905, y: 2744639.3474106374},
      {x: 2021.800789083797,  y: 368942.81801370444},
      {x: 2021.7323739864744, y: 22223.831271251252},
      {x: 2021.7050082248777, y: 3903.8535247797586},
      {x: 2021.6913253440794, y: 1171.0567890987963},
      {x: 2021.6913253440794, y: 524.7682899711657},
      {x: 2021.6913253440794, y: 268.81654607418335},
      {x: 2021.6913253440794, y: 179.95078883590534},
      {x: 2021.6913253440794, y: 137.7042528950963},
      {x: 2021.7186904123455, y: 80.63812827609142},
      {x: 2021.7597397480713, y: 53.98029487388338},
      {x: 2021.9102514368533, y: 36.13516703531101},
      {x: 2022.0333980573694, y: 27.652003886002543},
      {x: 2022.1154960354902, y: 24.189387992794604},
      {x: 2022.1839097461514, y: 24.189387992794604},
      {x: 2022.27968991174,   y: 41.30777428430223},
      {x: 2022.4986166978445, y: 179.95078883590534},
      {x: 2022.6764955348847, y: 783.921851127075},
      {x: 2022.8133236495376, y: 2613.2978856027025},
      {x: 2022.909104508457,  y: 6666.566190092435},
      {x: 2023.0048846740456, y: 14876.954663285154},
      {x: 2023.1143491070939, y: 37951.383479635304},
      {x: 2023.22381146015,   y: 74085.8678177873},
      {x: 2023.3059087449403, y: 126515.59245824469},
      {x: 2023.374323148932,  y: 165328.30625057936},
      {x: 2023.4290546721256, y: 216048.0642471517},
      {x: 2023.4837875819803, y: 282327.9253616156},
      {x: 2023.4974697694481, y: 322741.84031484334},
      {x: 2023.5111526502465, y: 322741.84031484334},
      {x: 2023.5111526502465, y: 322741.84031484334},
      {x: 2023.5111526502465, y: 322741.84031484334},
      {x: 2023.5111526502465, y: 322741.84031484334},
      {x: 2023.5111526502465, y: 322741.84031484334},
      {x: 2023.5111526502465, y: 322741.84031484334},
      {x: 2023.5111526502465, y: 322741.84031484334},
      {x: 2023.4974697694481, y: 482127.85491758876},
      {x: 2023.3880067230612, y: 1229916.9771181485},
      {x: 2023.1143491070939, y: 7001625.180988207},
      {x: 2022.9638360316503, y: 17861261.804262567},
      {x: 2022.8543729852634, y: 34867615.77990286},
      {x: 2022.731227058078,  y: 68066012.04237115},
      {x: 2022.5807146759653, y: 116235553.33103722},
      {x: 2022.45756874878,   y: 151894535.5661333},
      {x: 2022.3617885831914, y: 198493053.7487312},
      {x: 2022.2660077242722, y: 259387161.22768515},
      {x: 2022.1702275586836, y: 296518821.7510353},
      {x: 2022.101812461361,  y: 387485738.72836334},
      {x: 2022.0333980573694, y: 387485738.72836334},
      {x: 2021.93761719845,   y: 387485738.72836334},
      {x: 2021.8555206069905, y: 338964326.5788661},
      {x: 2021.787105509668,  y: 259387161.22768515},
      {x: 2021.7734233222002, y: 259387161.22768515},
      {x: 2021.7597397480713, y: 259387161.22768515},
      {x: 2022.4165194130542, y: 259387161.22768515},
      {x: 2022.3891543447883, y: 427414879260455.56},
      {x: 2022.3617885831914, y: 8111297256177839.0},
      {x: 2022.3617885831914, y: 1.759680835429727e+17},
      {x: 2022.3617885831914, y: 7.665729820345338e+17},
      {x: 2022.3207392474658, y: 1.9555416995467113e+18},
      {x: 2022.252324150143,  y: 4.3639415520325975e+18},
      {x: 2022.1428617970869, y: 9.73844445972882e+18},
      {x: 2022.0744466997644, y: 1.6630185565163446e+19},
      {x: 2022.0197151765708, y: 2.173208704259477e+19},
      {x: 2022.0060322957725, y: 2.484293311412861e+19},
      {x: 2021.9649829600467, y: 3.246429925553145e+19},
      {x: 2021.951300772579,  y: 4.242387882165013e+19},
      {x: 2021.9239350109824, y: 4.849669046546367e+19},
      {x: 2021.9102514368533, y: 6.337477680832235e+19},
      {x: 2021.8965692493855, y: 7.244657810757697e+19},
      {x: 2021.8828856752566, y: 8.281702124729395e+19},
      {x: 2021.8828856752566, y: 8.281702124729395e+19},
      {x: 2021.8828856752566, y: 8.281702124729395e+19},
      {x: 2022.5533496076994, y: 864703611.3515831},
      {x: 2022.9775189124487, y: 953806556139813.8},
      {x: 2023.0459340097711, y: 1.0599726461794412e+16},
      {x: 2023.1417141753598, y: 1.3465719500985659e+17},
      {x: 2023.2374950342792, y: 1.710666507260605e+18},
      {x: 2023.3332751998676, y: 2.173208704259477e+19},
      {x: 2023.4427382462545, y: 1.848120448270681e+20},
      {x: 2023.6069335091656, y: 2.683909019576515e+21},
      {x: 2023.7437616238187, y: 2.2824317704868853e+22},
      {x: 2023.839542482738,  y: 1.2993368296914504e+23},
      {x: 2023.9079568867298, y: 3.789108576696036e+23},
      {x: 2023.9626884099232, y: 8.455675451969181e+23},
      {x: 2024.0311035072457, y: 1.6506607781619025e+24},
      {x: 2024.0858350304393, y: 3.222308108879204e+24},
      {x: 2024.1268836728343, y: 4.813624957699226e+24},
      {x: 2024.1405672469634, y: 5.502683926615925e+24},
      {x: 2024.154249434431,  y: 6.290367255992265e+24},
      {x: 2022.909104508457,  y: 506359411.0058614},
      {x: 2023.1280312945614, y: 8406186392.619398},
      {x: 2023.6069335091656, y: 3460844650299.824},
      {x: 2024.003737745649,  y: 2433176322562153.5},
      {x: 2023.0185682481745, y: 17861261.804262567},
      {x: 2023.3880067230612, y: 506359411.0058614},
      {x: 2022.6217633183605, y: 2205862767.0467453},
      {x: 2022.6764955348847, y: 36620048415.39027},
      {x: 2022.6764955348847, y: 327074340150485.6},
      {x: 2022.6764955348847, y: 1.2117035676942778e+16},
      {x: 2022.5807146759653, y: 9.73844445972882e+18},
      {x: 2022.5807146759653, y: 4.7145858738634824e+20},
      {x: 2022.4302029871833, y: 3.507293661835237e+21},
      {x: 2022.3207392474658, y: 9.943026245129466e+22},
      {x: 2022.2386419626755, y: 4922552246.995361},
      {x: 2022.1291782229578, y: 794442019199.8861},
      {x: 2021.9649829600467, y: 218947708710401.62},
      {x: 2021.6913253440794, y: 9.01414774435516e+16},
      {x: 2021.294521107596,  y: 3.246429925553145e+19},
      {x: 2021.1303258446849, y: 2.1126759289595704e+20},
      {x: 2020.9113990585804, y: 1.0520960611756806e+21},
      {x: 2021.7597397480713, y: 96814.08279743527},
      {x: 2021.8692034877888, y: 37951.383479635304},
      {x: 2021.7734233222002, y: 37951.383479635304},
      {x: 2021.8555206069905, y: 322741.84031484334},
      {x: 2021.8555206069905, y: 216048.0642471517},
      {x: 2021.8828856752566, y: 110672.63853603433},
      {x: 2021.9239350109824, y: 74085.8678177873},
      {x: 2022.0060322957725, y: 551142.5639087047},
      {x: 2022.0470809381677, y: 246974.49616770382},
      {x: 2022.1428617970869, y: 144625.80265930557},
      {x: 2022.252324150143,  y: 84690.96884160757},
      {x: 2022.3617885831914, y: 49594.18699813736},
      {x: 2022.5123002719736, y: 22223.831271251252},
      {x: 2022.5807146759653, y: 14876.954663285154},
      {x: 2022.6901777223522, y: 9958.80175214076},
      {x: 2022.7585928196747, y: 8711.749084043708},
      {x: 2022.8133236495376, y: 8711.749084043708},
      {x: 2022.8680551727311, y: 8711.749084043708},
      {x: 2022.9227880825858, y: 8711.749084043708},
      {x: 2022.9775189124487, y: 8711.749084043708},
      {x: 2023.0185682481745, y: 8711.749084043708},
      {x: 2023.0459340097711, y: 8711.749084043708},
      {x: 2023.0459340097711, y: 8711.749084043708},
      {x: 2023.0459340097711, y: 8711.749084043708},
      {x: 2021.0459340097711, y: 1.2e24},
      {x: 2022.0459340097711, y: 1e24},
      {x: 2023.0459340097711, y: 2e24},
      {x: 2023.5459340097711, y: 2e24},
      {x: 2023.4459340097711, y: 3e24},
      {x: 2023.4459340097711, y: 3e24},
      {x: 2023.5,             y: 2e25},
    ];
    drawScatter(svg, scatterPoints, xScale, yScale);
  }

  class Scale {
    constructor(domain, range) {
      this.domain = domain;
      this.range = range;
    }

    apply(x) {
      let y = this.range[0] + (x - this.domain[0])/(this.domain[1] - this.domain[0]) * (this.range[1] - this.range[0]);
      return y;
    }

    invert(y) {
      let x = this.domain[0] + (y - this.range[0])/(this.range[1] - this.range[0]) * (this.domain[1] - this.domain[0]);
      return x;
    }
  }

  function createSvgElement(tag, attrs, props={}) {
    let elem = document.createElementNS('http://www.w3.org/2000/svg', tag);
    for (let key in attrs) {
      elem.setAttribute(key, attrs[key]);
    }
    for (let key in props) {
      elem[key] = props[key];
    }
    return elem;
  }

  function drawXAxis(svg, dataAreaBounds, xScale) {
    // Draw axis line
    let line = createSvgElement('line', {
      'x1': dataAreaBounds.x0,
      'x2': dataAreaBounds.x1,
      'y1': dataAreaBounds.y1,
      'y2': dataAreaBounds.y1,
      'class': 'axis'
    });
    svg.appendChild(line);

    for (let x of [2014, 2016, 2018, 2020, 2022, 2024]) {
      // Draw tick line
      let line = createSvgElement('line', {
        'x1': xScale.apply(x),
        'x2': xScale.apply(x),
        'y1': dataAreaBounds.y1,
        'y2': dataAreaBounds.y1 + 5,
        'class': 'tick',
      });
      svg.appendChild(line);

      // Draw tick label
      let text = createSvgElement('text', {
        'x': xScale.apply(x),
        'y': dataAreaBounds.y1 + 12,
        'text-anchor': 'middle',
        'dominant-baseline': 'hanging',
        'class': 'tick-label',
      }, {
        'textContent': x
      });
      svg.appendChild(text);
    }
  }

  function drawYAxis(svg, dataAreaBounds, yScale) {
    // Draw axis line
    let line = createSvgElement('line', {
      'y1': dataAreaBounds.y0,
      'y2': dataAreaBounds.y1,
      'x1': dataAreaBounds.x0,
      'x2': dataAreaBounds.x0,
      'class': 'axis'
    });
    svg.appendChild(line);

    // Keep track of the last bounding box to prevent overlapping labels
    let lastBBox = null;

    for (let y of [1, 0.5e25, 1e25, 1.5e25, 2e25].reverse()) {
      // Draw tick line
      let line = createSvgElement('line', {
        'y1': yScale.apply(y),
        'y2': yScale.apply(y),
        'x1': dataAreaBounds.x0,
        'x2': dataAreaBounds.x0 - 5,
        'class': 'tick',
      });
      svg.appendChild(line);

      // Draw tick label
      let text = createSvgElement('text', {
        'y': yScale.apply(y),
        'x': dataAreaBounds.x0 - 8,
        'dominant-baseline': 'middle',
        'text-anchor': 'end',
        'class': 'tick-label',
      }, {
        'textContent': y
      });
      svg.appendChild(text);

      // Remove overlapping ticks
      let bBox = text.getBBox();
      if (lastBBox != null && bBox.y < lastBBox.y + lastBBox.height) {
        svg.removeChild(text);
        svg.removeChild(line);
      }

      lastBBox = bBox;
    }
  }

  function drawLine(svg, points, xScale, yScale) {
    let line = createSvgElement('path', {
      'd': `M ${points.map(p => xScale.apply(p.x) + ' ' + yScale.apply(p.y)).join(' L ')}`,
      'stroke': '#32ab24',
      'stroke-width': 3,
      'fill': 'none',
    });
    svg.appendChild(line);
  }

  function drawScatter(svg, points, xScale, yScale) {
    // Draw a little 'X' at each point
    for (let point of points) {
      let pointX = xScale.apply(point.x);
      let pointY = yScale.apply(point.y);
      let pointR = 3;
      svg.appendChild(createSvgElement('path', {
        'd': `M ${pointX-pointR} ${pointY-pointR} L ${pointX+pointR} ${pointY+pointR} M ${pointX-pointR} ${pointY+pointR} L ${pointX+pointR} ${pointY-pointR}`,
        'stroke': 'black',
        'stroke-width': 1.3,
        'fill': 'none',
      }));
    }
  }

  // Actually draw the thing
  drawFigure();
  body {
    background-color: #4a4c63;
    font-family: Arial;
  }

  .figure {
    margin-left: 45px;
    display: inline-block;
  }

  .title {
    font-size: 26px;
    color: white;
    margin-bottom: 10px;
    text-align: center;
  }

  .y-axis-label {
    transform: translateX(-50%) rotate(-90deg);
    position: absolute;
    text-align: center;
    padding-right: 15px;
    padding-left: 10px;
    color: white;
    font-size: 20px;
    top: 50%;
    left: -25px;
  }

  svg {
    width: 640px;
    height: 373px;
    background-color: #ece6ea;

    /* Subtly draw attention to the figure with a border */
    border: 4px solid #f947ae;
    border-radius: 24px;
  }

  svg .axis {
    stroke: #000;
    stroke-width: 1px;
  }

  svg .tick {
    stroke: #000;
    stroke-width: 1px;
  }

  svg .tick-label {
    font-size: 14px;
  }
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<div class="figure">
  <div class="title">KEY TRENDS AND FIGURES <br> IN MACHINE LEARNING</div>
  <div style="position: relative">
    <span class="y-axis-label">Training compute (FLOP)</span>
    <svg id="chart"></svg>
  </div>
</div>
</body>
</html>