Home > OS >  How could I get each field in the circle to be the same size and clickable?
How could I get each field in the circle to be the same size and clickable?

Time:09-02

For the following code: enter image description here

CodePudding user response:

Here's a variable <svg> solution REPL
Unfortunately not every field is seperately clickable which I just saw you mentioned in a comment. That's a crucial fact which you might add to your question.

Edit: By changing to fill: none; actually every field is seperately clickable, thanks @herrstrietzel

<script>
    const size = 300
    let strokeWidth = 25
    let gap = 20

    $: circumference = Math.PI * (size - strokeWidth)

    let pieces = [
        {stroke: 'teal'},
        {stroke: 'magenta'},
        {stroke: 'orange'},
    ]

    let color = '#1411DF'

</script>

<div style:width="{size}px">
    <svg width={size} height={size}>
        {#each pieces as piece, index}
        {@const ownLength = circumference / pieces.length - gap}
        <circle r={size/2 - strokeWidth/2}
                        cx={size/2}
                        cy={size/2}
                        style:stroke-width={strokeWidth}
                        style:stroke={piece.stroke}
                        style:stroke-dasharray="{ownLength} {circumference - ownLength}"
                        style="fill: none; transform-origin: center;"
                        style:transform="rotate({index * 360 / pieces.length}deg)"
                        on:click={() => console.log(piece.stroke)}
                        />
        {/each}
    </svg>

    <input type="color" bind:value={color}>
    <button on:click={() => pieces = [...pieces, {stroke: color}]}>
        add piece
    </button>

    <label>
        stroke-width:
        <input type="range" bind:value={strokeWidth} min="1" max={size/5}>
        {strokeWidth}
    </label>

    <label>
        gap:
        <input type="range" bind:value={gap} min="1" max={size/5}>
        {gap}
    </label>

</div>

<style>
    circle:hover {
        stroke: black !important;
    }
    div {
        margin: 0 auto;
        display: flex;
        flex-direction: column;
        align-items: center;
    }
    svg {
        display: block;
        margin-bottom: 2rem;
    }
    label {
        width: 100%;
        font-size: .9rem;
        padding: 1rem 0;
    }
    input {
        padding: 0;
    }
</style>

CodePudding user response:

I could do some complicated calculations with position relative and absolute, but I wonder if there's an easy solution for my problem?

I'm afraid there is no solution completely without calculations, but at least this one is not complicated.

You can use conic-gradient and split it by degrees (in my example by 60deg) but the corners tend to be too pixelated. You can also use repeating-conic-gradient.

div {
  position: relative;
  width: 200px;
  height: 200px;
  border-radius: 100%;
  background: conic-gradient( 
     /* per 60deg - 5*2deg for white space */
     white 5deg,
     red 5deg 55deg, white 55deg 65deg,
     orange 65deg 115deg, white 115deg 125deg,
     blue 125deg 175deg, white 175deg 185deg,
     pink 185deg 235deg, white 235deg 245deg,
     gray 245deg 295deg, white 295deg 305deg,
     yellow 305deg 355deg, white 355deg 360deg
  );
}
div:after {
  content: '';
  position: absolute;
  left: 20px;
  top: 20px;
  width: 160px;
  height: 160px;
  background: white;
  border-radius: 100%;
}
<div></div>

CodePudding user response:

You could use an SVG. You can create path elements with arcs and stroke them in the respective color.

Arcs are fairly complicated with many parameters:

 A rx ry x-axis-rotation large-arc-flag sweep-flag x y
 a rx ry x-axis-rotation large-arc-flag sweep-flag dx dy

I would recommend reading the MDN documentation or the spec.

Example of a segment:

<svg width="320" height="320" xmlns="http://www.w3.org/2000/svg">
  <path d="M 50 50 a 50 50 0 0 1 50 0" stroke="black" stroke-width="20" fill="none"/>
</svg>


The easiest method is probably calculating the angles (start/end) and converting from polar to cartesian. The paths only need two commands: Absolute move to start location & absolute arc to end location.

Full example with clickable segments and keyboard support for additional accessibility (could still be improved, e.g. by supplying screen reader texts):

<script>
    let radius = 150;
    let stroke = 20;
    let gap = 5;
    let segments = [
        '#ff0000',
        '#00ff00',
        '#0000ff',
    ];
    
    function getCoordinates(i, gap) {
        const angleDelta = 360 / segments.length;
        
        const start = polarToCartesian(radius, i * angleDelta   gap);
        const end = polarToCartesian(radius, i * angleDelta   angleDelta);

        return { start, end };
    }
    
    const polarToCartesian = (r, angle) => {
        return {
            x: r * Math.cos(rad(angle)),
            y: r * Math.sin(rad(angle)),
        }
    }
    const rad = x => x * Math.PI / 180;
    const onClick = i => alert('Segment '   i);
</script>

<div>
    <svg width="320" height="320" xmlns="http://www.w3.org/2000/svg">
        <g transform="translate(160, 160)">
            {#each segments as segment, i (i)}
                {@const { start, end } = getCoordinates(i, gap)}
                <path d="M {start.x} {start.y}
                         A {radius} {radius} 0 0 1 {end.x} {end.y}"
                      stroke={segment} stroke-width={stroke} fill="none"
                      tabindex={0}
                      on:keydown={e => { if (e.key == 'Enter') onClick(i); }}
                      on:click={() => onClick(i)} />
            {/each}
        </g>
    </svg>
</div>

REPL

  • Related