Desenho em círculo com o caminho do arco do SVG

146

Pergunta curta: usando o caminho SVG, podemos desenhar 99,99% de um círculo e ele aparece, mas quando é 99,99999999% de um círculo, o círculo não aparece. Como pode ser consertado?

O seguinte caminho SVG pode desenhar 99,99% de um círculo: (tente em http://jsfiddle.net/DFhUF/1381/ e veja se você vê 4 arcos ou apenas 2 arcos, mas observe que, se for o IE, é processado em VML, não SVG, mas tem o mesmo problema)

M 100 100 a 50 50 0 1 0 0.00001 0

Mas quando é 99,99999999% de um círculo, nada será mostrado?

M 100 100 a 50 50 0 1 0 0.00000001 0    

E é o mesmo com 100% de um círculo (ainda é um arco, não é, apenas um arco muito completo)

M 100 100 a 50 50 0 1 0 0 0 

Como isso pode ser corrigido? O motivo é que eu uso uma função para desenhar uma porcentagem de um arco, e se eu precisar "aplicar um caso especial" a um arco de 99,9999% ou 100% para usar a função de círculo, isso seria meio bobo.

Novamente, um caso de teste no jsfiddle usando o RaphaelJS está em http://jsfiddle.net/DFhUF/1381/
(e se for VML no IE 8, mesmo o segundo círculo não será exibido ... é necessário alterá-lo para 0,01)


Atualizar:

Isso ocorre porque eu estou renderizando um arco para uma pontuação em nosso sistema, então 3,3 pontos recebem 1/3 de um círculo. 0,5 obtém meio círculo e 9,9 pontos obtêm 99% de um círculo. Mas e se houver pontuações de 9,99 no nosso sistema? Preciso verificar se está próximo a 99,999% de um círculo e usar uma arcfunção ou uma circlefunção de acordo? Então, que tal uma pontuação de 9.9987? Qual usar? É ridículo precisar saber que tipo de pontuação será mapeada para um "círculo muito completo" e mudar para uma função de círculo. Quando houver "um certo 99,9%" de um círculo ou uma pontuação de 9.9987, use a função de arco. .

太極 者 無極 而 生
fonte
@ Minitech, é claro que funciona ... é então 99% de um círculo (a grosso modo). O caso é que ele pode desenhar 98%, 99%, 99,99% de um círculo, mas não 99.9999999% ou 100%
nonopolarity
1
Ambos os links vão para a mesma coisa e funciona bem no Safari.
MarkBessey
certo, mesmo link, eu só quero que as pessoas vejam o caso de teste mais cedo, então adiciono o link no início da pergunta. O safari certo vai fazer isso, que legal ... Chrome e Firefox não ... meio estranho porque o Safari e o Chrome são Webkit ... mas o mecanismo SVG depende do Webkit?
nonopolarity
@Marcin parece bem como? você vê 4 arcos ou 2 arcos? você olhou o código?
nonopolarity
2
um jsFiddle atualizado com Raphael incluído como uma biblioteca, para contornar cruz de origem erro ao carregar Raphael.js: jsfiddle.net/DFhUF/1381
ericsoco

Respostas:

35

O mesmo para o arco do XAML. Basta fechar o arco de 99,99% com um Ze você terá um círculo!

Todd Main
fonte
então você pode fechar o terceiro caso na amostra jsfiddle e fazê-lo funcionar?
nonopolarity
com a redução do valor para 0,0001, sim. o problema parece relacionado à implementação do WebKit de vários navegadores, não ao próprio SVG. Mas é assim que você faz um círculo no componente Arc SVG / XAMLs.
Todd Principal
Ahhh, trapaça, sempre a melhor solução. Ainda é necessário lidar com o caso de 100%, mas apenas "arredondando para 99,999%" em vez de com um ramo de código separado.
precisa saber é o seguinte
Alguma demonstração? Esta resposta não cumpre
Medet Tleukabiluly
@MedetTleukabiluly você tem o arco acima na pergunta, adicione um zno final e pronto. Pode ser necessário remover zeros, por exemplo, no Chrome mais recente, tive que escrever 0.0001para fazê-lo funcionar. Se você estiver procurando onde colocar a string do caminho, consulte Paths on MDN .
TWIStErRob #
415

Eu sei que é um pouco tarde no jogo, mas lembrei-me desta pergunta de quando era nova e eu tinha um dilema semelhante e acidentalmente encontrei a solução "certa", se alguém ainda estivesse procurando por uma:

<path 
    d="
    M cx cy
    m -r, 0
    a r,r 0 1,0 (r * 2),0
    a r,r 0 1,0 -(r * 2),0
    "
/>

Em outras palavras, isso:

<circle cx="100" cy="100" r="75" />

pode ser alcançado como um caminho com isso:

  <path 
        d="
        M 100, 100
        m -75, 0
        a 75,75 0 1,0 150,0
        a 75,75 0 1,0 -150,0
        "
  />

O truque é ter dois arcos, o segundo pegando onde o primeiro parou e usando o diâmetro negativo para voltar ao ponto inicial do arco original.

A razão pela qual isso não pode ser feito como um círculo completo em um arco (e estou apenas especulando) é porque você estaria dizendo para desenhar um arco de si mesmo (digamos 150.150) para si mesmo (150.150), o que ele renderiza como "oh, eu já estou lá, não é necessário!".

Os benefícios da solução que estou oferecendo são:

  1. é fácil traduzir de um círculo diretamente para um caminho e
  2. não há sobreposição nas duas linhas do arco (o que pode causar problemas se você estiver usando marcadores ou padrões, etc.). É uma linha contínua limpa, embora desenhada em duas partes.

Nada disso importaria se eles permitissem apenas que os caminhos de texto aceitassem formas. Mas acho que eles estão evitando essa solução, já que elementos de forma como círculo não têm tecnicamente um ponto de "início".

Demonstração do jsfiddle: http://jsfiddle.net/crazytonyi/mNt2g/

Atualizar:

Se você estiver usando o caminho para uma textPathreferência e desejar que o texto seja renderizado na borda externa do arco, use exatamente o mesmo método, mas altere o sinalizador de varredura de 0 para 1 para que ele trate a parte externa do caminho como a superfície em vez do interior (pense 1,0em alguém sentado no centro e desenhando um círculo em volta de si, enquanto 1,1alguém andando pelo centro a uma distância de raio e arrastando o giz ao lado deles, se isso for de alguma ajuda). Aqui está o código acima, mas com a alteração:

<path 
    d="
    M cx cy
    m -r, 0
    a r,r 0 1,1 (r * 2),0
    a r,r 0 1,1 -(r * 2),0
    "
/>
Anthony
fonte
1
+1, obrigado pelas fórmulas. Obviamente, os dois primeiros comandos podem ser consolidados.
John Gietzen
+1 pela clareza e praticidade da solução em geral, mas principalmente por 'elementos de forma como o círculo não possuem tecnicamente um ponto de início' - uma maneira clara de expressar o problema.
David John Welsh
11
Eu acho que o problema aqui não é que "oh, eu já estou lá, não é necessário!", Pois ao fornecer 1 como bandeira de arco grande, você diz explicitamente ao mecanismo que deseja que ele siga adiante. O verdadeiro problema é que existem infinitos círculos que preenchem restrições de passagem através de seu argumento. Isso explica por que, quando você deseja um arco de 360 ​​*, o mecanismo não sabe o que fazer. A razão por que não funciona para 359,999 é que esta é a computação numericamente instável, e enquanto não tecnicamente é exatamente um círculo que corresponde a solicitação, ele provavelmente não seria o que você queria de qualquer maneira
qbolec
@qbolec - Você está certo, eu estava pensando em termos do arco grande e da varredura estarem fora e o arco não ter destino. Ou, no mínimo, que mesmo com arco grande e varredura permitiram que houvesse algum projeto fundamental que definisse um arco como a curva entre dois pontos (de modo que, com um ponto usado duas vezes, ele fosse visto como um ponto único em colapso). a visão do arco que potencialmente desenha 360 círculos em torno de um ponto faz sentido, embora desmoronasse em um círculo se um centro fosse definido, o que levanta a questão de um único arco poderia fazer um círculo completo se um centro existisse?
Anthony
1
"elementos de forma como círculo não possuem tecnicamente um ponto" inicial ". Isso não é verdade. A especificação SVG especifica absolutamente onde está o ponto inicial de todas as formas. É necessário fazer isso para que as matrizes do traço funcionem nas formas.
Paul LeBeau
17

Em referência à solução de Anthony, aqui está uma função para obter o caminho:

function circlePath(cx, cy, r){
    return 'M '+cx+' '+cy+' m -'+r+', 0 a '+r+','+r+' 0 1,0 '+(r*2)+',0 a '+r+','+r+' 0 1,0 -'+(r*2)+',0';
}
Anton Matiashevich
fonte
10

Uma abordagem totalmente diferente:

Em vez de mexer nos caminhos para especificar um arco no svg, você também pode pegar um elemento circle e especificar um stroke-dasharray, no pseudo-código:

with $score between 0..1, and pi = 3.141592653589793238

$length = $score * 2 * pi * $r
$max = 7 * $r  (i.e. well above 2*pi*r)

<circle r="$r" stroke-dasharray="$length $max" />

Sua simplicidade é a principal vantagem sobre o método de vários caminhos de arco (por exemplo, ao criar scripts, você apenas insere um valor e está pronto para qualquer comprimento de arco)

O arco começa no ponto mais à direita e pode ser deslocado usando uma transformação de rotação.

Nota: O Firefox tem um bug estranho, onde rotações acima de 90 graus ou mais são ignoradas. Então, para iniciar o arco de cima, use:

<circle r="$r" transform="rotate(-89.9)" stroke-dasharray="$length $max" />
mvds
fonte
esteja ciente de que esta solução é aplicável apenas aos casos em que você não está usando nenhum tipo de preenchimento e precisa apenas de um segmento de arco sem desvios. No momento em que você deseja desenhar setores, você precisa de um novo nó de caminho svg, que poderia ser tratado em uma única marca de caminho.
Mxfh
@mxfh não mesmo, se você usar a largura do traço duas vezes o valor de r, obterá setores perfeitos.
Mvds
Com stroke-dasharray="0 10cm 5cm 1000cm"ele é possível evitar a transformação
stenci
@stenci sim, mas a desvantagem é que você não pode ter um parâmetro na dasharray a escala de 0% a 100% de preenchimento (que era um dos meus objetivos)
MVDs
5

O Adobe Illustrator usa curvas mais bege como SVG e, para círculos, cria quatro pontos. Você pode criar um círculo com dois comandos de arco elíptico ... mas para um círculo no SVG eu usaria um <circle />:)

Phrogz
fonte
"Você pode criar um círculo com dois comandos de arco elíptico"? O que você quer dizer? Você não pode simplesmente usar um? Pelo menos, passando de (0,0) para (0,01, 0) para que renderize alguma coisa. Para usar circle, consulte a atualização na pergunta.
nonopolarity
Não, acho que você não pode usar apenas um. Você demonstrou que não pode e tem um problema semelhante com os arcos do HTML5 Canvas.
Phrogz
você quer dizer, usando arcpara criar um círculo completo usando o arco esquerdo e o arco direito? Então arco não pode desenhar um círculo completo ... para fazê-lo duas arcsão necessários caminhos
nonopolarity
2
@ 動靜 能量 Correto, são necessários dois caminhos de arco (ou o feio hack de um arco na maior parte do caminho e, em seguida, um comando de caminho próximo a linha reta até o fim).
Phrcz #
1
Illustrator é uma merda. Você não pode fazer um círculo com curvas mais bege. Apenas algo próximo a um círculo, mas ainda não um círculo.
Adius
4

É uma boa ideia usar o comando two arc para desenhar um círculo completo.

geralmente, uso elipse ou elemento de círculo para desenhar um círculo completo.

cuixiping
fonte
5
Uma grande limitação do svg é que elementos de forma não podem ser usados ​​para caminhos de texto. Esta é provavelmente a principal motivação para todos que visitam esta pergunta.
Anthony
1
@ Anthony eles podem agora. O Firefox suporta textPath contra qualquer forma. Eu suspeito que o Chrome também.
Robert Longson 08/07/19
@RobertLongson - Você pode fornecer um exemplo ou links para um exemplo? Curioso como decide onde o caminho do texto começa e se está do lado de fora ou do lado de dentro (etc) quando é desenhado sem um ponto de partida tradicional.
187 Anthony Anthony
@Anthony Veja o SVG 2 especificação por exemplo w3.org/TR/SVG2/shapes.html#CircleElement , também o novo textPath atributo lado
Robert Longson
4

Com base nas respostas de Anthony e Anton, incorporei a capacidade de girar o círculo gerado sem afetar sua aparência geral. Isso é útil se você estiver usando o caminho para uma animação e precisar controlar onde ela começa.

function(cx, cy, r, deg){
    var theta = deg*Math.PI/180,
        dx = r*Math.cos(theta),
        dy = -r*Math.sin(theta);
    return "M "+cx+" "+cy+"m "+dx+","+dy+"a "+r+","+r+" 0 1,0 "+-2*dx+","+-2*dy+"a "+r+","+r+" 0 1,0 "+2*dx+","+2*dy;
}
rosscowar
fonte
Isso é exatamente o que eu precisava. Eu estava usando o plug-in greensock morph para transformar um caminho de círculo em um caminho retangular e precisava de uma leve rotação para fazer com que parecesse correto.
RoboKozo 29/07/19
3

Para aqueles como eu, que estavam procurando por uma elipse atribui à conversão de caminho:

const ellipseAttrsToPath = (rx,cx,ry,cy) =>
`M${cx-rx},${cy}a${rx},${ry} 0 1,0 ${rx*2},0a${rx},${ry} 0 1,0 -${rx*2},0`
iogue
fonte
2

Escrito como uma função, fica assim:

function getPath(cx,cy,r){
  return "M" + cx + "," + cy + "m" + (-r) + ",0a" + r + "," + r + " 0 1,0 " + (r * 2) + ",0a" + r + "," + r + " 0 1,0 " + (-r * 2) + ",0";
}
Harry Stevens
fonte
2
Esta é uma ótima resposta! Apenas uma pequena adição: esse caminho é desenhado para o leste, norte, oeste e sul. Se quiser que o círculo se comportar exatamente como um <circle>, você tem que mudar a direção para Leste, Sul, Oeste, Norte: "M" + cx + "," + cy + "m" + (r) + ",0" + "a" + r + "," + r + " 0 1,1 " + (-r * 2) + ",0" + "a" + r + "," + r + " 0 1,1 " + (r * 2) + ",0" A vantagem é que stroke-dashoffsete startOffsetagora funcionam da mesma maneira.
Loominade 14/05
2

Essas respostas são muito complicadas.

Uma maneira mais simples de fazer isso sem criar dois arcos ou converter em diferentes sistemas de coordenadas.

Isso pressupõe que sua área da tela tenha largura we altura h.

`M${w*0.5 + radius},${h*0.5}
 A${radius} ${radius} 0 1 0 ${w*0.5 + radius} ${h*0.5001}`

Basta usar a bandeira "arco longo", para que a bandeira completa seja preenchida. Então faça dos arcos 99,9999% o círculo completo. Visualmente é o mesmo. Evite a bandeira de varredura apenas iniciando o círculo no ponto mais à direita do círculo (um raio diretamente horizontal do centro).

Estatísticas de aprendizado por exemplo
fonte
1

Outra maneira seria usar duas curvas de Bezier cúbicas. Isso é para o pessoal do iOS que usa pocketSVG, que não reconhece o parâmetro svg arc.

C x1 y1, x2 y2, xy (ou c dx1 dy1, dx2 dy2, dx dy)

Curva cúbica de Bezier

O último conjunto de coordenadas aqui (x, y) é onde você deseja que a linha termine. Os outros dois são pontos de controle. (x1, y1) é o ponto de controle para o início da sua curva e (x2, y2) para o ponto final da sua curva.

<path d="M25,0 C60,0, 60,50, 25,50 C-10,50, -10,0, 25,0" />
ha100
fonte
1

Eu fiz um jsfiddle para fazer aqui:

function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
var angleInRadians = (angleInDegrees-90) * Math.PI / 180.0;

return {
x: centerX + (radius * Math.cos(angleInRadians)),
y: centerY + (radius * Math.sin(angleInRadians))
};
}

function describeArc(x, y, radius, startAngle, endAngle){

var start = polarToCartesian(x, y, radius, endAngle);
var end = polarToCartesian(x, y, radius, startAngle);

var largeArcFlag = endAngle - startAngle <= 180 ? "0" : "1";

var d = [
    "M", start.x, start.y, 
    "A", radius, radius, 0, largeArcFlag, 0, end.x, end.y
].join(" ");

return d;       
}
console.log(describeArc(255,255,220,134,136))

ligação

tudo o que você precisa fazer é alterar a entrada do console.log e obter o resultado no console

javad bat
fonte
Era exatamente isso que eu estava procurando. Melhor resposta aqui! Upvote esse cara
Måns Dahlström