CSS Looks Different in PM Visualizer Browser vs App

Hello, does anybody know why my css looks different in postman visualizer in the browser vs. the desktop app? In the app, my css margins are working and make space between two elements. In the browser, they’re all scrunched up. I’m also using an external library to make a word cloud and I don’t know why it looks different in the app and the browser.

CSS margins in app:


CSS margins in browser:

Word cloud in app:

Word cloud in browser:

can you share your entire Test script?

const template = `
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<script src="https://cdn.anychart.com/releases/v8/js/anychart-base.min.js"></script>
<script src="https://cdn.anychart.com/releases/v8/js/anychart-tag-cloud.min.js"></script>
<script src="https://cdn.anychart.com/releases/8.10.0/js/anychart-map.min.js" type="text/javascript"></script>
<script src="https://cdn.anychart.com/releases/8.10.0/js/anychart-core.min.js" type="text/javascript"></script>
<script src="https://cdn.anychart.com/releases/8.10.0/geodata/countries/united_states_of_america/united_states_of_america.js"></script>
<script src="https://cdn.anychart.com/releases/8.10.0/geodata/countries/india/india.js"></script>
<script src="https://cdn.anychart.com/releases/8.10.0/geodata/custom/world/world.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.3.15/proj4.js" data-export="true"></script>

<style>
    body { text-align: center; }
    h1 { padding: 10px; font-size: 4vw; }
    h2 { font-size: 2vw; }
    p { font-size: 1.5vw; }
    a { font-size: 1.5vw; }
    .card-title { font-size: 2vw; text-align: left;}
    .card-subtitle { font-size: 1.5vw; text-align: left; }
    #word-cloud {width: 100%; height: 100%; margin: 0; padding: 0;}
    @media (min-width: 300px) { .change-size { width: 35rem;}}
</style>

<body>
<h1>Trending News From {{country}}</h1>
<div id="carouselExampleControls" class="carousel slide" data-bs-ride="carousel" style="width:80vw; margin: 0 auto; height:30vh;">
  <div class="carousel-inner">
    <div class="carousel-item active">
        <img src="{{firstResponse.image.url}}" class="d-block w-100" style="max-height: 300px;">
        <div class="carousel-caption d-none d-md-block">
            <h5>{{firstResponse.description}}</p>
        </div>
      </div>
      {{#each firstTenResponses}}
      <div class="carousel-item">
        <img src={{image.url}} class="d-block w-100" style="max-height: 300px;">
        <div class="carousel-caption d-none d-md-block">
        <h5>{{description}}</p>
      </div>
      </div>
      {{/each}}
  </div>
  <button class="carousel-control-prev" type="button" data-bs-target="#carouselExampleControls" data-bs-slide="prev">
    <span class="carousel-control-prev-icon" aria-hidden="true"></span>
    <span class="visually-hidden">Previous</span>
  </button>
  <button class="carousel-control-next" type="button" data-bs-target="#carouselExampleControls" data-bs-slide="next">
    <span class="carousel-control-next-icon" aria-hidden="true"></span>
    <span class="visually-hidden">Next</span>
  </button>
</div>

<div class="container-fluid">
    <div class="row row-cols-3" style="margin: 225 20 30 20">
        {{#each firstTenResponses}}
        <div class="col">
            <div class="card change-size h-100 w-100">
            <div class="card-body">
                <h5 class="card-title">{{title}}</h5>
                <h6 class="card-subtitle mb-2 text-muted">{{provider.name}} - {{datePublished}}</h6>
                <p class="card-text" style="text-align: left;">{{snippet}}</p>
                <a href="{{url}}" class="card-link">Read the full story</a>
            </div>
            </div>
        </div>
        {{/each}}
    </div>
</div>

<div class="accordion accordion-flush" id="accordionFlush">
    {{#each twentiethResponses}}
        <div class="accordion-item">
            <h2 class="accordion-header" id="flush-heading{{@index}}">
            <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#flush-collapse{{@index}}" aria-expanded="false" aria-controls="flush-collapse{{@index}}" style="font-size: 2vw;">
            {{title}}
            </button>
            </h2>
            <div id="flush-collapse{{@index}}" class="accordion-collapse collapse" aria-labelledby="flush-heading{{@index}}" data-bs-parent="#accordionFlush">
            <div class="accordion-body" style="margin: 30; text-align: left;">{{#each body}}<p style="font-size: 2vw;">{{paragraph}}</p>{{/each}}</div>
            </div>
        </div>
    {{/each}}
</div>
<p id="test"></p>

<div id="word-cloud">
    <script>
    anychart.onDocumentReady(function() {
  var decodedJSON = decodeURIComponent("{{{wordFrequency}}}");
  var data = JSON.parse(decodedJSON)

  var chart = anychart.tagCloud(data);

  chart.title('Frequency of Words in Title of Top News Stories')
  chart.angles([0])
  chart.container("word-cloud");
  chart.draw();
});
    </script>
</div>

<div id="bar-chart">
    <script>
        anychart.onDocumentReady(function() {
            var decodedJSON = decodeURIComponent("{{{providerFrequency}}}")
            var data = JSON.parse(decodedJSON)
            var chart = anychart.bar()
            var series = chart.bar(data)
            chart.container("bar-chart")
            chart.barGroupsPadding(0)
            chart.title('News Provider Frequency')
            var xAxis = chart.xAxis()
            xAxis.title('News Provider')
            var yAxis = chart.yAxis()
            yAxis.title('Number of Trending News Stories From Provider')
            chart.draw()
        })
    </script>
</div>

<div id="map">
    <script>
        anychart.onDocumentReady(function() {
            const country = "{{country}}"
            let anychartCountry = ''
            if (country === 'The United States') {
                anychartCountry = 'united_states_of_america'
            } else if (country === 'India') {
                anychartCountry =  'india'
            } else if (country === 'The World') {
                anychartCountry = 'world'
            } else {
                anychartCountry = null
            }

            if (anychartCountry === 'united_states_of_america' || anychartCountry === 'india' || anychartCountry === 'world') {
                    var map = anychart.map();
                    
                    var decodedJSON = decodeURIComponent("{{{stateFrequency}}}")
                    var data = JSON.parse(decodedJSON)
                    var dataSet = anychart.data.set(data);

                    map.geoData(anychart.maps[anychartCountry]);
                    map.title().useHtml(true).hAlign('center');
                    map.title(\`<span style="font-size: 18px;">Number of Times Provinces are Mentioned in The Trending News Stories from ${"{{country}}"}\`);
                    
                    series = map.choropleth(dataSet);
                    series.geoIdField('id');
                    series.colorScale(anychart.scales.linearColor('#deebf7', '#3182bd'));
                    series.hovered().fill('#addd8e');
                    
                    map.container('map');

                    map.draw();
            }
        })
    </script>
</div>

  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
  
</body>
`

const setCountry = (countryAbbreviation) => {
    let countryName = ''
    if (countryAbbreviation === 'us') {
        countryName = 'The United States'
    } else if (countryAbbreviation === 'in') {
        countryName = 'India'
    } else if (countryAbbreviation === 'uk') {
        countryName = 'The United Kingdom'
    } else {
        countryName = 'The World'
    }

    return countryName
}

const formatCard = (newsJSONList) => {
    newsJSONList.forEach((news) => {
        news.provider.name = news.provider.name.toUpperCase()
        news.datePublished = news.datePublished.slice(0, news.datePublished.indexOf('T'))
    })

    return newsJSONList
}

const formatAccordion = (newsJSONList) => {
    newsJSONList.forEach((news) => {
        const paragraphs = news.body.split('\n')
        if (paragraphs[0] === 'Share this article' && paragraphs[1] === 'Share') {
            paragraphs.shift()
            paragraphs.shift()
        }
        const paragraphJSON = []
        paragraphs.forEach((paragraph) => {
            paragraphJSON.push({"paragraph": paragraph})
        })
        news.body = paragraphJSON
    })

    return newsJSONList
}

const getWordsInTitleFrequency = (arr) => {
    const frequency = {}
    const ignore = ['the', 'and', 'a', 'or', 'to', 'of', 'for', 'in', 'as', 'is', 'at', 'on', 'it', 'if', 'about', 'an', 'how', 'with', 'be', '2021', 'your', 'her', 'his', 'she', 'he', 'news']
    arr.forEach((title) => {
        words = title.split(' ')
        words.forEach((word) => {
            const formatted = word.toLowerCase().replace(/[.,\/#!$%\^&\*;:{}=\-_`~()']/g,"")
            if (ignore.includes(formatted)) {}
            else if (formatted in frequency) {
                frequency[formatted] += 1
            } else {
                frequency[formatted] = 1
            }
        })
    })
    const data = []

    for (const [key, value] of Object.entries(frequency)) {
        data.push({'x': key, 'value': value})
    }

    return data
}

const getProviderFrequency = (arr) => {
    const counts = arr.reduce((counts, provider) => {
        if (counts.hasOwnProperty(provider)) {
            counts[provider] += 1
        } else {
            counts[provider] = 1
        }
        return counts
    }, {})
    const data = []

    for (const [key, value] of Object.entries(counts)) {
        data.push([key, value])
    }
    
    return data
}

const getStateFrequency = (arr, country) => {
    let states = {}
    if (country === 'The United States') {
        states = {"AL":"Alabama","AK":"Alaska", "AZ":"Arizona","AR":"Arkansas","CA":"California","CO":"Colorado","CT":"Connecticut","DE":"Delaware","FL":"Florida","GA":"Georgia","HI":"Hawaii","ID":"Idaho","IL":"Illinois","IN":"Indiana","IA":"Iowa","KS":"Kansas","KY":"Kentucky","LA":"Louisiana","ME":"Maine","MD":"Maryland","MA":"Massachusetts","MI":"Michigan","MN":"Minnesota","MS":"Mississippi","MO":"Missouri","MT":"Montana","NE":"Nebraska","NV":"Nevada","NH":"New Hampshire","NJ":"New Jersey","NM":"New Mexico","NY":"New York","NC":"North Carolina","ND":"North Dakota","OH":"Ohio","OK":"Oklahoma","OR":"Oregon","PA":"Pennsylvania","RI":"Rhode Island","SC":"South Carolina","SD":"South Dakota","TN":"Tennessee","TX":"Texas","UT":"Utah","VT":"Vermont","VA":"Virginia","WA":"Washington","WV":"West Virginia","WI":"Wisconsin","WY":"Wyoming"};
    } else if (country === 'India') {
        states = {"AN":"Andaman and Nicobar Islands", "AP":"Andhra Pradesh","AR":"Arunachal Pradesh", "AS":"Assam", "BR":"Bihar","CG":"Chandigarh", "CH":"Chhattisgarh", "DN":"Dadra and Nagar Haveli", "DD":"Daman and Diu", "DL":"Delhi", "GA":"Goa","GJ":"Gujarat", "HR":"Haryana", "HP":"Himachal Pradesh", "JK":"Jammu and Kashmir", "JH":"Jharkhand", "KA":"Karnataka", "KL":"Kerala","LA":"Ladakh", "LD":"Lakshadweep", "MP":"Madhya Pradesh","MH":"Maharashtra", "MN":"Manipur", "ML":"Meghalaya", "MZ":"Mizoram","NL":"Nagaland", "OR":"Odisha", "PY":"Puducherry", "PB":"Punjab","RJ":"Rajasthan", "SK":"Sikkim", "TN":"Tamil Nadu", "TS":"Telangana","TR":"Tripura", "UP":"Uttar Pradesh", "UK":"Uttarakhand", "WB":"West Bengal"}
    } else if (country === 'The World') {
        states = {"AF": "Afghanistan", "AX": "Åland Islands", "AL": "Albania","DZ": "Algeria", "AS": "American Samoa", "AD": "Andorra", "AO": "Angola", "AI": "Anguilla", "AQ": "Antarctica", "AG": "Antigua and Barbuda", "AR": "Argentina", "AM": "Armenia", "AW": "Aruba", "AU": "Australia", "AT": "Austria", "AZ": "Azerbaijan", "BS": "Bahamas","BH": "Bahrain", "BD": "Bangladesh", "BB": "Barbados", "BY": "Belarus", "BE": "Belgium", "BZ": "Belize", "BJ": "Benin", "BM": "Bermuda", "BT": "Bhutan", "BO": "Bolivia", "BA": "Bosnia and Herzegovina", "BW": "Botswana", "BV": "Bouvet Island", "BR": "Brazil", "IO": "British Indian Ocean Territory", "BN": "Brunei Darussalam", "BG": "Bulgaria", "BF": "Burkina Faso", "BI": "Burundi","KH": "Cambodia", "CM": "Cameroon", "CA": "Canada", "CV": "Cape Verde", "KY": "Cayman Islands","CF": "Central African Republic", "TD": "Chad", "CL": "Chile", "CN": "China", "CX": "Christmas Island", "CC": "Cocos (Keeling) Islands", "CO": "Colombia", "KM": "Comoros", "CG": "Congo", "CD": "Congo, Democratic Republic", "CK": "Cook Islands", "CR": "Costa Rica","CI": "Cote D\"Ivoire", "HR": "Croatia", "CU": "Cuba", "CY": "Cyprus", "CZ": "Czech Republic", "DK": "Denmark", "DJ": "Djibouti", "DM": "Dominica", "DO": "Dominican Republic", "EC": "Ecuador", "EG": "Egypt", "SV": "El Salvador", "GQ": "Equatorial Guinea", "ER": "Eritrea", "EE": "Estonia", "ET": "Ethiopia", "FK": "Falkland Islands (Malvinas)", "FO": "Faroe Islands", "FJ": "Fiji", "FI": "Finland", "FR": "France","GF": "French Guiana", "PF": "French Polynesia", "TF": "French Southern Territories","GA": "Gabon", "GM": "Gambia", "GE": "Georgia", "DE": "Germany", "GH": "Ghana", "GI": "Gibraltar", "GR": "Greece", "GL": "Greenland", "GD": "Grenada", "GP": "Guadeloupe", "GU": "Guam", "GT": "Guatemala", "GG": "Guernsey", "GN": "Guinea", "GW": "Guinea-Bissau", "GY": "Guyana", "HT": "Haiti", "HM": "Heard Island and Mcdonald Islands", "VA": "Holy See (Vatican City State)", "HN": "Honduras", "HK": "Hong Kong","HU": "Hungary", "IS": "Iceland", "IN": "India", "ID": "Indonesia", "IR": "Iran","IQ": "Iraq", "IE": "Ireland", "IM": "Isle of Man", "IL": "Israel", "IT": "Italy","JM": "Jamaica", "JP": "Japan", "JE": "Jersey", "JO": "Jordan", "KZ": "Kazakhstan","KE": "Kenya", "KI": "Kiribati", "KP": "Korea (North)", "KR": "Korea (South)", "XK": "Kosovo", "KW": "Kuwait", "KG": "Kyrgyzstan", "LA": "Laos", "LV": "Latvia", "LB": "Lebanon", "LS": "Lesotho", "LR": "Liberia", "LY": "Libyan Arab Jamahiriya", "LI": "Liechtenstein", "LT": "Lithuania", "LU": "Luxembourg", "MO": "Macao", "MK": "Macedonia", "MG": "Madagascar", "MW": "Malawi", "MY": "Malaysia", "MV": "Maldives","ML": "Mali", "MT": "Malta", "MH": "Marshall Islands", "MQ": "Martinique", "MR": "Mauritania", "MU": "Mauritius", "YT": "Mayotte", "MX": "Mexico", "FM": "Micronesia","MD": "Moldova", "MC": "Monaco", "MN": "Mongolia", "MS": "Montserrat", "MA": "Morocco", "MZ": "Mozambique", "MM": "Myanmar", "NA": "Namibia", "NR": "Nauru", "NP": "Nepal","NL": "Netherlands", "AN": "Netherlands Antilles", "NC": "New Caledonia","NZ": "New Zealand", "NI": "Nicaragua", "NE": "Niger", "NG": "Nigeria", "NU": "Niue","NF": "Norfolk Island", "MP": "Northern Mariana Islands", "NO": "Norway", "OM": "Oman", "PK": "Pakistan", "PW": "Palau", "PS": "Palestinian Territory, Occupied","PA": "Panama", "PG": "Papua New Guinea", "PY": "Paraguay", "PE": "Peru", "PH": "Philippines", "PN": "Pitcairn", "PL": "Poland", "PT": "Portugal", "PR": "Puerto Rico", "QA": "Qatar", "RE": "Reunion", "RO": "Romania", "RU": "Russian Federation","RW": "Rwanda", "SH": "Saint Helena", "KN": "Saint Kitts and Nevis", "LC": "Saint Lucia", "PM": "Saint Pierre and Miquelon", "VC": "Saint Vincent and the Grenadines","WS": "Samoa", "SM": "San Marino", "ST": "Sao Tome and Principe", "SA": "Saudi Arabia", "SN": "Senegal", "RS": "Serbia", "ME": "Montenegro", "SC": "Seychelles","SL": "Sierra Leone", "SG": "Singapore", "SK": "Slovakia", "SI": "Slovenia", "SB": "Solomon Islands", "SO": "Somalia", "ZA": "South Africa", "GS": "South Georgia and the South Sandwich Islands", "ES": "Spain", "LK": "Sri Lanka", "SD": "Sudan","SR": "Suriname", "SJ": "Svalbard and Jan Mayen", "SZ": "Swaziland", "SE": "Sweden","CH": "Switzerland", "SY": "Syrian Arab Republic", "TW": "Taiwan, Province of China","TJ": "Tajikistan", "TZ": "Tanzania", "TH": "Thailand", "TL": "Timor-Leste", "TG": "Togo", "TK": "Tokelau", "TO": "Tonga", "TT": "Trinidad and Tobago", "TN": "Tunisia","TR": "Turkey", "TM": "Turkmenistan", "TC": "Turks and Caicos Islands", "TV": "Tuvalu","UG": "Uganda", "UA": "Ukraine", "AE": "United Arab Emirates", "GB": "United Kingdom","US": "United States", "UM": "United States Minor Outlying Islands","UY": "Uruguay","UZ": "Uzbekistan", "VU": "Vanuatu", "VE": "Venezuela", "VN": "Vietnam", "VG": "Virgin Islands, British", "VI": "Virgin Islands, U.S.", "WF": "Wallis and Futuna","EH": "Western Sahara", "YE": "Yemen", "ZM": "Zambia", "ZW": "Zimbabwe"}
    }
    const stateFrequency = arr.reduce((counts, story) => {
        for (const [key, value] of Object.entries(states)) {
            if (story.includes(value) && counts.hasOwnProperty(key)) {
                counts[key] += 1
            } else if (story.includes(value) && !(counts.hasOwnProperty(key))) {
                counts[key] = 1
            }
        }
        return counts
    }, {})

    const data = []

    for (const [key, value] of Object.entries(stateFrequency)) {
        if (country === 'The United States') {
            data.push({"id": `US.${key}`, "value": value})
        } else if (country === 'India') {
            data.push({"id": `IN.${key}`, "value": value})
        } else if (country === 'The World') {
            data.push({"id": `${key}`, "value": value})
        }
    }

    return data
}

pm.visualizer.set(template, {
    firstResponse: pm.response.json().value[0],
    firstTenResponses: formatCard(pm.response.json().value.slice(1, 10)),
    twentiethResponses: formatAccordion(pm.response.json().value.slice(10, 20)),
    country: setCountry(pm.request.url.query.toObject().location),
    wordFrequency: encodeURIComponent(JSON.stringify(getWordsInTitleFrequency(pm.response.json().value.map(x => x.title)))),
    providerFrequency: encodeURIComponent(JSON.stringify(getProviderFrequency(pm.response.json().value.map(x => x.provider.name.toUpperCase())))),
    stateFrequency: encodeURIComponent(JSON.stringify(getStateFrequency(pm.response.json().value.map(x => x.body), setCountry(pm.request.url.query.toObject().location))))
})

I think the problem with margins happens in my card div container, I gave it a margin of 225 20 30 20 and it seems to be giving that margin on the desktop app but not on browser.

Here’s the link to my collection too if that’s helpful.

Thanks for the link!

Try removing the fixed height on your carousel at the top.

Since you have it set to a fixed height of 30vh, the height of your carousel gets squished when the visualizer window is shorter (you can see the height is different in your web vs desktop screenshot - page zoom also comes into play)

Thank you, this fixed the cards going over my carousel! However, my CSS margins still don’t seem to be recognized in the browser. I want a little space between my carousel and cards, so I added margin to my card container. It works in the app but not browser.

Also, my charts still look really weird in the browser, any idea why that’s happening? I didn’t make the css of the charts so idk what’s going on there

Thanks so much for your help

Sounds a little tricky!

You can right click on the visualizer window and Inspect the visualization using browser dev tools. Click the elements in question may reveal where the strange CSS is being applied in web vs desktop

Thank you, I wasn’t aware I could inspect my visualizations and this helped me solve my margin problem :slight_smile: Appreciate it! The charts are proving to be quite tricky to debug though…

I’m getting this error three times, and I’m assuming it relates to my charts because I have three of them:

‘The Content-Security-Policy directive name ‘frame-ancestors:’ contains one or more invalid characters. Only ASCII alphanumeric characters or dashes ‘-’ are allowed in directive names.’

Any idea on what to fix with this (or even where to start looking - I really have no idea)?

Postman for web has some Content Security Policies set in place to protect from malicious use in the browser setting.

These are not settings you can change on the client side, so you can remove this code from your template

<head>
    <meta http-equiv="Content-Security-Policy" content="frame-ancestors 'all'">
</head>

Is anything broken in your visualization? If you are trying to figure out exactly which elements are introducing the CSP violations, you can try commenting each component out one by one

Yes, all my visualizations are broken and are much smaller than usual. They’re not scaling correctly like they do in the desktop app. I’m guessing the third party library uses iframe to scale the visualization and the browser is blocking it?

I have a feeling the errors are coming from the inline <script> tags in your html template. You are using three separate script tags and calling the same window object (anychart), reassigning the onDocumentReady callback. TI’m not familiar with how anychart works under the hood, but this might be causing it to get confused

Maybe refactoring to have one <script> handle the definition of the callback for anychart.onDocumentReady() would help

See this for example of using multiple anychart charts on the same page:
http://6.anychart.com/products/anychart/docs/users-guide/several-charts.html