chroma-harmony
Version:
A minimal Color Harmony library built on top of [Chroma.js](https://www.npmjs.com/package/chroma-js)
780 lines (710 loc) • 25.3 kB
HTML
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Chroma-Harmony Visualizer</title>
<meta name="description" content="" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="https://unpkg.com/css.gg@2.0.0/icons/css/moon.css" rel="stylesheet" />
<link href="https://unpkg.com/css.gg@2.0.0/icons/css/sun.css" rel="stylesheet" />
<style>
:root {
--border-radius: 10px;
--side-margin: 30px;
--bottom-margin: 30px;
--main-color: red;
--text-color-2: gray;
}
[data-theme='dark'] {
--bg-color: #222;
--text-color-1: white;
}
[data-theme='light'] {
--bg-color: #eee;
--text-color-1: black;
}
*,
*:before,
*:after {
box-sizing: border-box;
}
* {
color: var(--text-color-1);
white-space: nowrap;
}
div {
display: flex;
flex-grow: 1;
}
p {
text-align: center;
margin: 10px 0;
}
h3 {
margin: 0;
}
label {
cursor: pointer;
font-family: system-ui, sans-serif;
line-height: 1.1;
display: grid;
grid-template-columns: 1em auto;
gap: 0.5em;
}
input[type='radio'] {
appearance: none;
margin: 0;
background-color: var(--bg-color);
font: inherit;
color: currentColor;
width: 1.15em;
height: 1.15em;
border: 0.15em solid currentColor;
border-radius: 50%;
transform: translateY(-0.075em);
display: grid;
place-content: center;
}
input[type='radio']::before {
content: '';
width: 0.65em;
height: 0.65em;
border-radius: 50%;
transform: scale(0);
transition: 100ms transform ease-out;
box-shadow: inset 1em 1em var(--main-color);
}
input[type='radio']:checked::before {
transform: scale(1);
}
input[type='range'] {
-webkit-appearance: none;
height: 7px;
border-radius: 5px;
outline: none;
opacity: 0.7;
-webkit-transition: .2s;
transition: opacity .2s;
border: gray 1px solid;
cursor: pointer;
}
input[type='range']::-webkit-slider-runnable-track {
-webkit-appearance: none;
color: #13bba4;
}
input[type='range']::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 15px;
height: 15px;
border-radius: 50%;
background: var(--bg-color);
border: var(--text-color-1) 3px solid;
}
input[type='range']::-moz-range-thumb {
width: 15px;
height: 15px;
border-radius: 50%;
background: var(--bg-color);
border: var(--text-color-1) 3px solid;
}
input[type="range"]::-moz-range-progress {
background-color: var(--main-color);
height: 7px;
border: black 1px solid;
border-radius: 5px;
}
html,
body {
background-color: var(--bg-color);
overflow: hidden;
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans',
'Helvetica Neue', sans-serif;
height: 100%;
padding: 0;
margin: 0;
}
body {
display: grid;
grid-template-columns: 1fr 5fr 1fr;
grid-template-rows: 0.5fr 6fr 2fr;
grid-template-areas:
'header header header'
'menu main right'
'menu footer right';
gap: 10px;
}
#header {
padding: 0 var(--side-margin);
background-color: black;
grid-area: header;
display: grid;
grid-template-columns: 1fr 5fr 1fr;
color: white;
}
#title {
background: linear-gradient(65deg, #a683e3 10%, #e4e9fd 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
#demos {
justify-content: center;
align-items: center;
}
#demos > svg {
cursor: pointer;
margin: 0 15px;
}
#settings {
padding: 0 var(--side-margin);
justify-content: center;
align-items: center;
}
#settings > i {
color: white;
cursor: pointer;
}
#options,
#harmonies {
display: flex;
flex-direction: column;
justify-content: space-between;
margin: 50px 10px;
}
#harmonies {
grid-area: menu;
padding: 5px 0;
padding-left: var(--side-margin);
}
#harmonies > div {
flex-grow: 0;
}
#example {
grid-area: main;
color: var(--text-color-2);
}
#options {
grid-area: right;
padding-right: var(--side-margin);
}
#color-input {
--color-picker-background-color: var(--bg-color);
--color-picker-color: var(--text-color-1);
}
#color-space {
display: flex;
flex-direction: column;
justify-content: space-around;
}
#angle,
#subdivisions {
justify-content: space-around;
flex-grow: 0;
margin-top: 10px;
}
#angle > div,
#subdivisions > div {
display: flex;
justify-content: space-evenly;
align-items: center;
margin-top: 10px;
}
#angle-input,
#subdivision-input {
margin-top: 10px;
width: 80%;
}
#canvas-bin {
display: flex;
grid-area: footer;
--border: black 2px solid;
}
#canvas-bin > div > div {
border-top: var(--border);
border-bottom: var(--border);
}
#canvas-bin > div:first-child > div {
border-top-left-radius: var(--border-radius);
border-bottom-left-radius: var(--border-radius);
border-left: var(--border);
}
#canvas-bin > div:last-child > div {
border-top-right-radius: var(--border-radius);
border-bottom-right-radius: var(--border-radius);
border-right: var(--border);
}
.flex-column {
flex-direction: column;
}
.flex-row {
flex-direction: row;
}
</style>
<script type="module" src="https://cdn.jsdelivr.net/npm/chroma-js"></script>
<script type="module" src="https://cdn.jsdelivr.net/npm/color-picker-web-component"></script>
<script src="https://cdn.plot.ly/plotly-2.20.0.min.js"></script>
</head>
<body>
<div id="header">
<h2 id="title">chroma-harmony.js</h2>
<div id="demos">
<svg id="bar" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" width="24" height="24">
<!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
<path
fill="white"
d="M160 80c0-26.5 21.5-48 48-48h32c26.5 0 48 21.5 48 48V432c0 26.5-21.5 48-48 48H208c-26.5 0-48-21.5-48-48V80zM0 272c0-26.5 21.5-48 48-48H80c26.5 0 48 21.5 48 48V432c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V272zM368 96h32c26.5 0 48 21.5 48 48V432c0 26.5-21.5 48-48 48H368c-26.5 0-48-21.5-48-48V144c0-26.5 21.5-48 48-48z"
/>
</svg>
<svg id="pie" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512" width="24" height="24">
<!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
<path
fill="white"
d="M304 240V16.6c0-9 7-16.6 16-16.6C443.7 0 544 100.3 544 224c0 9-7.6 16-16.6 16H304zM32 272C32 150.7 122.1 50.3 239 34.3c9.2-1.3 17 6.1 17 15.4V288L412.5 444.5c6.7 6.7 6.2 17.7-1.5 23.1C371.8 495.6 323.8 512 272 512C139.5 512 32 404.6 32 272zm526.4 16c9.3 0 16.6 7.8 15.4 17c-7.7 55.9-34.6 105.6-73.9 142.3c-6 5.6-15.4 5.2-21.2-.7L320 288H558.4z"
/>
</svg>
<svg id="map" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="24" height="24">
<!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
<path
fill="white"
d="M383.8 351.7c2.5-2.5 105.2-92.4 105.2-92.4l-17.5-7.5c-10-4.9-7.4-11.5-5-17.4 2.4-7.6 20.1-67.3 20.1-67.3s-47.7 10-57.7 12.5c-7.5 2.4-10-2.5-12.5-7.5s-15-32.4-15-32.4-52.6 59.9-55.1 62.3c-10 7.5-20.1 0-17.6-10 0-10 27.6-129.6 27.6-129.6s-30.1 17.4-40.1 22.4c-7.5 5-12.6 5-17.6-5C293.5 72.3 255.9 0 255.9 0s-37.5 72.3-42.5 79.8c-5 10-10 10-17.6 5-10-5-40.1-22.4-40.1-22.4S183.3 182 183.3 192c2.5 10-7.5 17.5-17.6 10-2.5-2.5-55.1-62.3-55.1-62.3S98.1 167 95.6 172s-5 9.9-12.5 7.5C73 177 25.4 167 25.4 167s17.6 59.7 20.1 67.3c2.4 6 5 12.5-5 17.4L23 259.3s102.6 89.9 105.2 92.4c5.1 5 10 7.5 5.1 22.5-5.1 15-10.1 35.1-10.1 35.1s95.2-20.1 105.3-22.6c8.7-.9 18.3 2.5 18.3 12.5S241 512 241 512h30s-5.8-102.7-5.8-112.8 9.5-13.4 18.4-12.5c10 2.5 105.2 22.6 105.2 22.6s-5-20.1-10-35.1 0-17.5 5-22.5z"
/>
</svg>
</div>
<div id="settings">
<i id="light-dark">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="24" height="24">
<!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
<path
fill="white"
d="M223.5 32C100 32 0 132.3 0 256S100 480 223.5 480c60.6 0 115.5-24.2 155.8-63.4c5-4.9 6.3-12.5 3.1-18.7s-10.1-9.7-17-8.5c-9.8 1.7-19.8 2.6-30.1 2.6c-96.9 0-175.5-78.8-175.5-176c0-65.8 36-123.1 89.3-153.3c6.1-3.5 9.2-10.5 7.7-17.3s-7.3-11.9-14.3-12.5c-6.3-.5-12.6-.8-19-.8z"
/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="24" height="24" style="display: none">
<!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
<path
fill="white"
d="M361.5 1.2c5 2.1 8.6 6.6 9.6 11.9L391 121l107.9 19.8c5.3 1 9.8 4.6 11.9 9.6s1.5 10.7-1.6 15.2L446.9 256l62.3 90.3c3.1 4.5 3.7 10.2 1.6 15.2s-6.6 8.6-11.9 9.6L391 391 371.1 498.9c-1 5.3-4.6 9.8-9.6 11.9s-10.7 1.5-15.2-1.6L256 446.9l-90.3 62.3c-4.5 3.1-10.2 3.7-15.2 1.6s-8.6-6.6-9.6-11.9L121 391 13.1 371.1c-5.3-1-9.8-4.6-11.9-9.6s-1.5-10.7 1.6-15.2L65.1 256 2.8 165.7c-3.1-4.5-3.7-10.2-1.6-15.2s6.6-8.6 11.9-9.6L121 121 140.9 13.1c1-5.3 4.6-9.8 9.6-11.9s10.7-1.5 15.2 1.6L256 65.1 346.3 2.8c4.5-3.1 10.2-3.7 15.2-1.6zM160 256a96 96 0 1 1 192 0 96 96 0 1 1 -192 0zm224 0a128 128 0 1 0 -256 0 128 128 0 1 0 256 0z"
/>
</svg>
</i>
</div>
</div>
<div id="harmonies">
<label>
<input type="radio" name="palette" value="sequential" checked />
Sequential
</label>
<label>
<input type="radio" name="palette" value="diverging" />
Diverging
</label>
<label>
<input type="radio" name="palette" value="qualitative" />
Qualitative
</label>
<label>
<input type="radio" name="palette" value="complementary" />
Complementary
</label>
<label>
<input type="radio" name="palette" value="splitComplementary" />
Split-Complementary
</label>
<label>
<input type="radio" name="palette" value="triadic" />
Triadic
</label>
<label>
<input type="radio" name="palette" value="tetradic" />
Tetradic
</label>
<label>
<input type="radio" name="palette" value="square" />
Square
</label>
<label>
<input type="radio" name="palette" value="analogous" />
Analogous
</label>
</div>
<div id="example"></div>
<div id="options">
<color-picker id="color-input" value="#ff0000" formats="hex,rgb,hsl,hsv" selectedformat="hex" no_alpha="true"></color-picker>
<div id="color-space">
<h3>Interpolation Mode</h3>
<label>
<input type="radio" name="color-space" value="HSL" checked/>
HSL
</label>
<label>
<input type="radio" name="color-space" value="HSV" />
HSV
</label>
<label>
<input type="radio" name="color-space" value="RGB" />
RGB
</label>
<label>
<input type="radio" name="color-space" value="LCH" />
LCH
</label>
<label>
<input type="radio" name="color-space" value="OKLCH" />
OKLCH
</label>
<select id="color-space-input" style="display: none">
<option value="hsl">HSL</option>
<option value="hsv">HSV</option>
<option value="rgb">RGB</option>
<option value="lch">LCH</option>
<option value="oklch">OKLCH</option>
</select>
</div>
<div id="subdivisions" class="flex-column">
<h3>Bin Count</h3>
<div>2<input type="range" id="subdivision-input" min="2" max="12" value="5" />12</div>
</div>
<div id="angle" class="flex-column" style="display: none">
<h3>Angle</h3>
<div>10<input type="range" id="angle-input" min="10" max="180" value="15" />180</div>
</div>
</div>
<div id="canvas-bin"></div>
<script>
let theme = 'dark';
const colorScheme = document.getElementById('light-dark');
const example = document.getElementById('example');
const toggleDisplayNone = (elm, def = 'block') => (elm.style.display = elm.style.display === 'none' ? def : 'none');
function detectColorScheme() {
//local storage is used to override OS theme settings
if (localStorage.getItem('theme')) {
theme = localStorage.getItem('theme');
} else if (!window.matchMedia) {
//matchMedia method not supported
return false;
} else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
//OS theme setting detected as dark
theme = 'dark';
} else {
theme = 'light';
}
document.documentElement.setAttribute('data-theme', theme);
if (theme !== 'dark') {
toggleDisplayNone(colorScheme.children.item(0));
toggleDisplayNone(colorScheme.children.item(1));
}
}
detectColorScheme();
colorScheme.addEventListener('click', (e) => {
theme = theme == 'dark' ? 'light' : 'dark';
localStorage.setItem('theme', theme);
document.documentElement.setAttribute('data-theme', theme);
toggleDisplayNone(colorScheme.children.item(0));
toggleDisplayNone(colorScheme.children.item(1));
if (example.layout)
Plotly.relayout(example, {
plot_bgcolor: theme == 'dark' ? '#222' : '#eee',
font: `var(--text-color-2)`,
});
});
</script>
<script type="module">
class Harmony {
constructor(space = 'hsl', correctLightness = false) {
this.space = space;
this.correctLightness = correctLightness;
}
/**
* Generate a sequential color scale for continous data
*/
sequential(color) {
if (color instanceof Array) {
return this.scale(color.map((c) => chroma(c))).correctLightness(this.correctLightness);
} else {
const [h, s] = chroma(color).hsl();
return this.scale([chroma(h, s, 0.95, 'hsl'), chroma(h, s, 0.2, 'hsl')]).correctLightness(this.correctLightness);
}
}
/**
* Generate a diverging color scale for when a critical data class or break point needs to be emphasized
* @param color The color to be used for 'high' values
*/
diverging(color) {
const [h, s] = chroma(color).hsl();
return this.scale([
chroma(h - 180, s, 0.2, 'hsl'),
chroma(h - 180, s, 0.5, 'hsl'),
chroma(h + 90, s, 0.95, 'hsl'),
chroma(h, s, 0.5, 'hsl'),
chroma(h, s, 0.2, 'hsl'),
]);
}
qualitative(color, count) {
// Round up to nearest integer just in case
count = Math.ceil(count);
if (count < 2) return [chroma(color)];
if (count == 2) {
return [chroma(color), this.shiftHue(color, 180)];
}
if (count == 3) return this.triadic(color);
if (count == 4) return this.tetradic(color);
if (count <= 12) return this.analogous(color, count, 160);
return this.sequential(color)
.colors(count)
.map((c) => chroma(c));
}
monochromatic(color, range = 2, variation = 'tints') {
const chromaColor = chroma(color);
let monochromeScaleEnd;
switch (variation) {
case 'shades':
monochromeScaleEnd = chromaColor.darken(range);
break;
case 'tones':
monochromeScaleEnd = chromaColor.desaturate(range);
break;
case 'tints':
monochromeScaleEnd = chromaColor.brighten(range);
break;
default:
throw TypeError(`Unknown monochromatic variation ${variation}`);
}
return this.scale([chromaColor, monochromeScaleEnd]).correctLightness(this.correctLightness);
}
/**
* Generates a color palette by selecting equidistant hues in the range [color.h - angle, color.h + angle]
* @param color The color used to generate the palette
* @returns an analogous color palette of n Chroma Colors
*/
analogous(color, count = 5, angle = 15) {
const startChroma = this.shiftHue(color, -angle);
const colors = [];
const angleInterval = (angle * 2) / (count - 1);
for (let i = 0; i < count; i++) {
colors.push(this.shiftHue(startChroma, angleInterval * i));
}
return colors;
}
/**
* Generates a complementary color palette range from the input color to the hue opposite color
* @param color The color used to generate the palette
* @returns a chroma {@link chroma.Scale}
*/
complementary(color) {
const [h, s] = chroma(color).hsl();
return this.scale([
chroma(h - 180, s, 0.2, 'hsl'),
chroma(h - 180, s, 0.5, 'hsl'),
chroma(h, s, 0.5, 'hsl'),
chroma(h, s, 0.2, 'hsl'),
]);
const chromaColor = chroma(color);
}
/**
* Generates a color palette by rotating the hue of the input color by [-angle, 0, angle]
* @param color The color used to generate the palette
* @returns a split complementary color palette of 3 Chroma Colors
*/
splitComplementary(color, angle = 150) {
return this.analogous(color, 3, angle);
}
/**
* Generates a color palette by rotating the hue of the input color by [-120, 0, 120]
* @param color The color used to generate the palette
* @returns a triadic color palette of 3 Chroma Colors
*/
triadic(color) {
return this.splitComplementary(color, 120);
}
/**
* Generates a color palette by rotating the hue of the input color by [0, 60, 180, 240]
* @param color The color used to generate the palette
* @returns a tetradic color palette of 4 Chroma Colors
*/
tetradic(color) {
return [chroma(color), this.shiftHue(color, 60), this.shiftHue(color, 180), this.shiftHue(color, 240)];
}
/**
* Generates a color palette by rotating the hue of the input color by [0, 90, 180, 270]
* @param color The color used to generate the palette
* @returns a square color palette of 4 Chroma Colors
*/
square(color) {
return [chroma(color), this.shiftHue(color, 90), this.shiftHue(color, 180), this.shiftHue(color, 270)];
}
scale(colors) {
return chroma.scale(colors).cache(false).mode(this.space);
}
get colorSpace() {
switch(this.space) {
case 'lab':
case 'lch':
case 'hcl':
return 'lch';
case 'oklch':
case 'oklab':
return 'oklch';
default:
return 'hsl';
}
}
shiftHue(color, angle) {
const hueIndex = this.colorSpace === 'hsl' ? 0 : 2;
const chromaColor = chroma(color)[this.colorSpace]();
chromaColor[hueIndex] += angle;
return chroma(chromaColor, this.colorSpace);
}
}
const harmony = new Harmony('hsl', true);
let mode = 'sequential';
let mainColor = 'red';
let count = 5;
let angle = 15;
document.getElementById('color-input').addEventListener('input', (e) => {
mainColor = e.target.value;
document.querySelector(':root').style.setProperty('--main-color', mainColor);
updateColor();
});
document.getElementById('color-input').addEventListener('change', (e) => {
mainColor = e.target.value;
let [h] = chroma(mainColor).hsl();
e.target.shadowRoot.getElementById('gridInput').style.background = `hsl(${h} 100% 50%)`;
document.querySelector(':root').style.setProperty('--main-color', mainColor);
updateColor();
});
const countInput = document.getElementById('subdivision-input');
countInput.addEventListener('input', (e) => {
count = e.target.value;
let value = (e.target.value-e.target.min)/(e.target.max-e.target.min)*100
e.target.style.background = 'linear-gradient(to right, var(--main-color) 0%, var(--main-color) ' + value + '%, #fff ' + value + '%, white 100%)'
updateColor();
});
const angleInput = document.getElementById('angle-input');
angleInput.addEventListener('input', (e) => {
angle = e.target.value;
let value = (e.target.value-e.target.min)/(e.target.max-e.target.min)*100
e.target.style.background = 'linear-gradient(to right, var(--main-color) 0%, var(--main-color) ' + value + '%, #fff ' + value + '%, white 100%)'
updateColor();
});
document.querySelectorAll('input[name="palette"]')[0].checked = true;
document.querySelectorAll('input[name="palette"]').forEach((elm) =>
elm.addEventListener('change', (e) => {
mode = e.target.value;
updateParams();
updateColor();
})
);
document.querySelectorAll('input[name="color-space"]')[0].checked = true;
document.querySelectorAll('input[name="color-space"]').forEach((elm) =>
elm.addEventListener('change', (e) => {
harmony.space = e.target.value.toLowerCase();
updateColor();
})
);
document.getElementById('color-space-input').addEventListener('change', (e) => {
harmony.space = e.target.value;
updateColor();
});
const title = document.getElementById('title');
const canvasBin = document.getElementById('canvas-bin');
function getParams() {
return harmony[mode].toString().split(')')[0].split('(')[1].split(', ');
}
let params;
let countParam;
let angleParam;
const updateParams = () => {
params = getParams();
countParam = mode == 'sequential' || mode == 'diverging' || mode == 'qualitative' || mode == 'complementary' || mode == 'analogous';
countInput.parentElement.parentElement.style.display = countParam ? 'flex' : 'none';
angleParam = params.find((param) => param.includes('angle'));
angleInput.parentElement.parentElement.style.display = angleParam ? 'flex' : 'none';
if (angleParam) {
const value = angleParam.split(' = ')[1];
angleInput.value = value;
angle = value;
}
};
const updateColor = () => {
let palette;
if (countParam && angleParam) {
palette = harmony[mode](mainColor, count, angle);
} else if (angleParam) {
palette = harmony[mode](mainColor, angle);
} else {
palette = harmony[mode](mainColor, count);
}
const colors = palette instanceof Array ? palette : palette.colors(count);
canvasBin.innerHTML = '';
let bins = palette instanceof Array ? palette.length : count;
for (let i = 0; i < bins; i++) {
const div = document.createElement('div');
const color = document.createElement('div');
const label = document.createElement('p');
color.style.backgroundColor = colors[i];
label.innerText = colors[i];
div.style.flexDirection = 'column';
div.appendChild(color);
div.appendChild(label);
canvasBin.appendChild(div);
}
title.style.background = `linear-gradient(65deg, ${colors.map((color, i) => color + ' ' + ((i + 0.5) / bins) * 100 + '%')})`;
title.style['-webkit-background-clip'] = 'text';
let data = example.data[0].y;
let plotlyColors;
console.log(data.map((x) => colors[Math.round(x * (colors.length - 1))].toString()));
if (mode === 'diverging') {
plotlyColors = data.map((x, i) => colors[Math.round((i/data.length) * (colors.length - 1))]);
} else {
plotlyColors = data.map((x) => colors[Math.round(x * (colors.length - 1))].toString());
}
Plotly.update(example, {
marker: {
color: plotlyColors,
},
});
};
const initInput = new Event('input')
countInput.dispatchEvent(initInput);
angleInput.dispatchEvent(initInput);
</script>
<script>
const config = {
staticPlot: true,
responsive: true,
};
const setupBar = () => {
const x = [];
for (let i = 0; i < 100; i++) {
x[i] = Math.random();
}
var data = [
{
y: x,
type: 'bar',
},
];
const layout = {
autosize: true,
paper_bgcolor: '#7f7f7f00',
plot_bgcolor: theme == 'dark' ? '#222' : '#eee',
font: {
color: `var(--text-color-2)`,
},
xaxis: {
zeroline: false,
visible: false,
},
yaxis: {
zeroline: false,
visible: false,
},
};
Plotly.newPlot(example, data, layout, config);
};
document.getElementById('bar').addEventListener('click', setupBar);
setupBar();
</script>
</body>
</html>