Einfache Interaktive Kreisdiagramm mit CSS-Variablen und Houdini Magic

0
1393

Ich habe die Idee für etwas, das der Art, wenn ich stolperte über dieses interaktive SVG-Kreisdiagramm. Während der SVG-code ist so kompakt wie es nur geht (ein einzelnes <circle> – element!), mit Anschlägen für die Erstellung Kreisdiagramm Scheiben ist problematisch, da wir in rendering-Probleme auf Windows für Firefox und Edge. Plus, im Jahr 2018, den wir erreichen können, viel mehr mit viel weniger JavaScript!

AI bekam das folgende Ergebnis bis auf eine einzige HTML-element für das Diagramm und sehr wenig JavaScript. Die Zukunft sollte vollständig beseitigen die Notwendigkeit für JavaScript, aber dazu später mehr.

Die endgültige Kreisdiagramm Ergebnis.

Einige von Euch erinnern sich vielleicht an Lea Verou Fehlt, Scheibe sprechen—meine Lösung basiert auf Ihre Technik. Dieser Artikel seziert, wie das alles funktioniert, zeigt, was wir tun können, in Bezug graceful degradation und andere Möglichkeiten, wie diese Technik verwendet werden können.

HTML

Wir verwenden Mops zum generieren des HTML-von einem Daten-Objekt, das enthält unitless prozentualen Werte für die letzten drei Jahre:

– var Daten = { 2016: 20, 2017: 26, 2018: 29 }

Wir machen alle unsere Elemente befinden sich in einem .wrap-element. Dann wir die Schleife durch unser Daten-Objekt und für jede seiner Eigenschaften, schaffen wir einen radio-Eingang mit einem entsprechenden label. Nach dieser, wir fügen Sie ein .pie element in den mix.

– var darr = [], val;

.wrap
– for(var p in Daten) {
– wenn(!val) val = data[p];
input(id=`o${p}` name=’o’ type=’radio’ checked=val == Daten[p])
label (=`o${p}`) #{p}
– darr.push(`${data[p]}% für das Jahr ${p}`)
– }
.pie(aria-label=`Wert als pie-chart. ${darr.join(‘, ‘)}.`
Rolle=’Grafik-Dokument-Gruppe’)

Beachten Sie, dass wir auch dafür gesorgt, dass nur die ersten radio-Eingang aktiviert ist.

Übergeben von benutzerdefinierten Eigenschaften, die auf die CSS

Ich normalerweise nicht wie putting-Stile in HTML-aber in diesem besonderen Fall, ist es eine sehr nützliche Möglichkeit für die übergabe von benutzerdefinierten Eigenschaftswerte, um die CSS und sicherzustellen, dass wir nur aktualisieren müssen, die Dinge in einem Ort, wenn wir ändern müssen unsere Daten-Punkte—die Mops-code. Das CSS bleibt gleich.

Der trick ist, um einheitenlosen Prozentsatz –p auf der .pie-element für jedes radio-Eingang, könnte überprüft werden:

Stil
– for(var p in Daten) {
| #o#{p}:checked ~ .pie { –p: #{data[p]} }
– }

Wir verwenden diesen Prozentsatz für eine Kegelschnitt-gradient() auf .pie-element, nachdem sicherstellen, dass weder von seinen Abmessungen (inklusive border und padding) 0:

$d: 20rem;

.wrap { width: $d; }

.pie {
Polsterung: 50%;
hintergrund: Kegelschnitt-gradient(#ab3e5b calc(var(–p)*1%), #ef746f 0%);
}

Beachten Sie, dass dies erfordert native Kegelschnitt-gradient () – Unterstützung, da das polyfill funktioniert nicht mit CSS-Variablen. In dem moment, das schränkt support für Blink-Browser mit der Experimental-Web-Platform-features – flag aktiviert, wenn die Dinge gebunden sind, um besser zu werden.

Die Experimental-Web-Platform-features flag in Chrom.

Jetzt haben wir eine funktionierende Skelett unserer demo—Kommissionierung ein anderes Jahr über die radio-buttons ergibt sich in einem anderen Kegelschnitt-gradient()!

Die grundlegende Funktionalität haben wir nach (live-demo, Blink-Browser mit dem flag aktiviert).

Anzeige des Wertes

Der nächste Schritt ist, um tatsächlich die Anzeige den aktuellen Wert, und wir tun dies über eine pseudo-element. Leider, Anzahl geschätzte CSS-Variablen können nicht verwendet werden, für die der Wert der content-Eigenschaft, so dass wir dies umgehen, indem mit dem counter() hack.

.Torten:after {
counter-reset: p var(–p);
content: counter(p) ‘%’;
}

Wir haben auch verändert die Farbe und schriftart-Größe-Eigenschaften, so dass unsere pseudo-element ist ein bisschen mehr sichtbar:

Anzeige der Wert auf dem Diagramm (live-demo, Blink-Browser mit dem flag aktiviert).

Glättung Dinge aus

Wir wollen nicht den abrupten Wechsel zwischen Werte, so dass wir glatt die Dinge mit der Hilfe von einem CSS-übergang. Bevor wir übergehen können, oder animieren Sie die –variable p, die wir brauchen, um es zu registrieren, die in JavaScript:

CSS.registerProperty({
name: ‘–p’,
syntax: ‘<integer>’,
initialValue: 0,
erbt: true
});

Beachten Sie, dass mit <Zahl> statt <Ganzzahl> bewirkt, dass der angezeigte Wert auf 0 gehen während der übergangsphase als der Zähler muss eine ganze Zahl. Dank Lea Verou, dass Sie mir helfen dies herauszufinden!

Beachten Sie auch, dass explizit die Einstellung erbt, ist dabei zwingend. Dies war nicht der Fall bis vor kurzem.

Dies ist alle der JavaScript, die wir brauchen für diese demo und, in die Zukunft, die wir gar nicht brauchen, so viel wie wir in der Lage sein zu registrieren, benutzerdefinierte Eigenschaften aus dem CSS.

Mit diesem aus dem Weg, wir können hinzufügen, eine Umstellung .pie-element.

.pie {
/* gleichen Stile wie zuvor */
übergang: – p .5s;
}

Und das ist es für die Funktionalität! Alles mit einem element, eine benutzerdefinierte variable, und ein streuen von Houdini magic!

Interaktive Kreisdiagramm (live-demo, Blink-Browser mit dem flag aktiviert).

Prettifying berührt

Während unsere demo ist funktional, es sieht alles andere als schön an dieser Stelle. Also, lassen Sie uns kümmern, wenn wir schon dabei sind!

Sie die Torte… die Torte!

Da die Anwesenheit von :nach zugenommen hat die Höhe der Ihre .pie Eltern, wir absolut positionieren. Und da wir wollen, dass unsere .pie element, Aussehen wie eine wirkliche Torte, machen wir es rund, mit border-radius: 50%.

Abgerundet wird unsere Torte (live-demo, Blink-Browser mit dem flag aktiviert).

Wir wollen auch den prozentualen Wert in der Mitte der dunkle Kreissegment.

Um dies zu tun, werden wir die erste position, die es, die tot in der Mitte von der .pie-element. Standardmäßig :nach dem pseudo-element angezeigt wird, nachdem seine Eltern Inhalt. Da .pie hat keinen Inhalt, in diesem Fall, die top-Links der :after-pseudo-element ist in der linken oberen Ecke des übergeordneten content-box. Hier wird der content-box ist ein 0x0 box in der Mitte des padding-box. Denken Sie daran, dass wir festgelegt haben, um die Polsterung .pie zu 50%—ein Wert, der relativ zu den wrapper-Breite für beide, die horizontale und die vertikale Richtung!

Dies bedeutet, dass die top-Links :nach der in der Mitte des übergeordneten Elements, so dass die translate(-50%, -50%) auf, es verschiebt es nach Links von der Hälfte seiner eigenen Breite und bis um die Hälfte seiner eigenen Höhe, die Ihre eigenen mid-point zeitgleich mit der .pie.

Denken Sie daran, dass %-geschätzt, die übersetzungen sind relativ zu den Abmessungen des Elements Sie angewendet werden, entlang der entsprechenden Achse. In anderen Worten, ein %-Wert translation entlang der x-Achse ist bezogen auf die Breite des Elements, ein %-Wert translation entlang der y-Achse ist relativ zu seiner Höhe und ein %-Wert translation entlang der z-Achse, relativ zu Ihrer Tiefe, die ist immer 0, weil alle Elemente sind flache zwei-dimensionale Felder mit 0 Tiefe entlang der Dritten Achse.

Die Positionierung des value label in der Mitte der Torte (live-demo, Blink-Browser mit dem flag aktiviert).

Als Nächstes drehen wir den Wert so, dass die positive Hälfte der x-Achse teilt die dunkle Scheibe in zwei gleiche Hälften, und dann übersetzen Sie es um die Hälfte einer Torte radius entlang dieser nun gedrehten x-Achse.

Finden Sie den Stift, indem Sie thebabydino (@thebabydino) auf CodePen.

Was wir brauchen, um herauszufinden, ist, wie viel zu drehen :nach dem pseudo-element, so dass die x-Achse teilt die dunkle Scheibe in zwei gleiche Hälften. Lassen Sie uns brechen, dass nach unten!

Zunächst die x-Achse ist in der horizontalen Richtung auf der rechten Seite, also, um es in die gewünschte Richtung, müssen wir zuerst drehen Sie es so, dass es Punkte auf und entlang der Start-slice-Kante. Dann braucht es zum drehen im Uhrzeigersinn, um die Hälfte einer Scheibe.

Um die Achse zu Punkt, wir brauchen, um es zu drehen von -90deg. Das minus-Zeichen ist aufgrund der Tatsache, dass positive Werte Folgen im Uhrzeigersinn Richtung, und wir gehen den anderen Weg.

Finden Sie den Stift, indem Sie thebabydino (@thebabydino) auf CodePen.

Als Nächstes müssen wir drehen Sie es um die Hälfte einer Scheibe.

Finden Sie den Stift, indem Sie thebabydino (@thebabydino) auf CodePen.

Aber wie viel ist die Hälfte einer Scheibe?

Gut, wir wissen schon, was Anteil von dem Kuchen in diesem Segment darstellt: es ist unsere benutzerdefinierte Eigenschaft, –p. Wenn wir teilen diesen Wert durch 100 und dann multipliziert das 360deg (oder 1turn, ist es egal, welche Einheit verwendet wird), bekommen wir den zentralen Winkel unseres dunklen Scheibe.

Nach der-90Grad Drehung, wir drehen müssen :nach der Hälfte dieser zentralen Winkel im Uhrzeigersinn (positive) Richtung.

Dies bedeutet, wenden wir die folgende Transformation-Kette:

translate(-50%, -50%) drehen(calc(.5*var(–p)/100*1turn – 90deg)) übersetzen(.25*$d);

Die Letzte übersetzung ist eine Viertel von $d, die wrapper-Breite und gibt uns die .Torte mit einem Durchmesser. (Da der content-box .pie ist ein 0x0 Feld, es hat keine Grenze, und beide, Links und rechts Polsterung sind 50% der wrapper übergeordneten Breite.) Die .pie radius ist der halbe Durchmesser, was bedeutet, dass die Hälfte des radius ist, ein Viertel des Durchmessers ($d).

Jetzt wird der Wert label genau dort ist, wo wir es haben wollen:

Die Positionierung des value label in der Mitte der Scheibe (live-demo, Blink-Browser mit dem flag aktiviert).

Es gibt allerdings noch ein problem: wir wollen es nicht gedreht werden kann, wie das Aussehen kann, wirklich peinlich und Hals-Biege-bei bestimmten Winkeln. Um dieses Problem zu beheben, haben wir wieder die Drehung am Ende. Um die Dinge einfacher für uns, wir speichern Sie den Winkel der Drehung in eine CSS-Variablen, wir nennen –ein:

–a: calc(.5*var(–p)/100*1turn – 90deg);
transformieren:
translate(-50%, -50%)
drehen(var (–))
übersetzen(.25*$d)
drehen(calc(-1*var (–)));

Viel besser!

Die Positionierung des value label in der Mitte der Scheibe, jetzt horizontal (live-demo, Blink-Browser mit dem flag aktiviert).

Layout

Wir wollen die ganze Versammlung in der Mitte des Bildschirms, so lösen wir dies mit einem netten kleinen Gitter-trick:

body {
display: grid;
Platz-items: center center;
margin: 0;
min-height: 100vh
}

Okay, das stellt die gesamte .wrap-element in der Mitte:

Positionierung die gesamte Montage in der Mitte (live-demo, Blink-Browser mit dem flag aktiviert).

Der nächste Schritt ist, um das Kreisdiagramm über die radio-buttons. Wir tun dies mit einem flexbox-layout auf die .wrap-element:

.wrap {
display: flex;
flex-wrap: wrap-reverse;
justify-content: center;
Breite: $d;
}

Platzieren Sie das Kreisdiagramm über die radio-buttons (live-demo, Blink-Browser mit dem flag aktiviert).

Das Styling der radio-buttons

…oder genauer gesagt, wir sind das styling der radio button labels, denn die erste Sache, die wir tun, ist zu verbergen die radio-Eingänge:

[type=’radio’] {
position: absolute;
Links: -100vw;
}

Nach dem ausblenden des radio-buttons (live-demo, Blink-Browser mit dem flag aktiviert).

Da diese Blätter, die uns mit einigen sehr hässlichen Etiketten, die sind sehr schwer voneinander zu unterscheiden, geben wir jede Art von margin und padding, so dass Sie sehen nicht so zusammengepfercht, plus hintergrund, so dass Ihre klickbare Bereiche deutlich hervorgehoben werden. Wir können sogar hinzufügen, box-und text-Schatten für einige 3D-Effekte. Und, natürlich, wir können erstellen Sie eine separate Fall, wenn Ihre entsprechenden Eingänge werden aktiviert.

$c: #ecf081 #b3cc57;

[type=’radio’] {
/* das gleiche wie vorher */

+ label {
margin: 3em .5em .5em;
Polsterung: .25em .5em;
border-radius: 5px;
box-shadow: 1px 1px N-te($c, 2);
hintergrund: nth($c, 1);
font-size: 1.25 em;
text-shadow: 1px 1px #fff;
cursor: pointer;
}

&:checked {
+ label {
box-shadow: inset -1px -1px N-te($c, 1);
hintergrund: nth($c, 2);
color: #fff;
text-shadow: 1px 1px #000;
}
}
}

Wir haben auch geblasen bis die font-Größe ein bisschen und setzen border-radius zu glätten die Ecken:

Nach dem styling der radio button labels (live-demo, Blink-Browser mit dem flag aktiviert).

Letzte prettifying berührt

Lassen Sie uns den hintergrund auf den Körper, regulieren die schriftart der ganzen Sache, und fügen Sie einen übergang für die Funk-Etiketten:

Die endgültige Kreisdiagramm Ergebnis (live-demo, Blink-Browser mit dem flag aktiviert).

Graceful degradation

Während unsere demo jetzt gut aussieht in der Blink-Browser mit dem flag aktiviert, es sieht schrecklich, in allen anderen Browsern…und das sind die meisten Browser!

First off, lassen wir unsere Arbeit in einer @unterstützt block, der überprüft, ob native Kegelschnitt-gradient () – Unterstützung, damit die Browser, die es unterstützen wird, verliert das Kreisdiagramm. Dazu gehören auch unsere konischen-gradient(), die Polsterung, das gibt der Torte den gleichen horizontalen und vertikalen Abmessungen, die border-radius macht die Torte kreisförmig, und die Transformations-Kette, die Positionen der Wert-label in der Mitte der Torte schneiden.

.pie {
@unterstützt (hintergrund: Kegelschnitt-gradient(tan, red)) {
Polsterung: 50%;
border-radius: 50%;
hintergrund: Kegelschnitt-gradient(var(–stop-Liste));
–a: calc(.5*var(–p)/100*1turn – 90deg);
–pos: drehen(var (–))
übersetzen(#{.25*$d})
drehen(calc(-1*var (–)));
}
}
}

Nun, lasst uns bauen Sie ein Balken-Diagramm als fallback für alle anderen Browser mithilfe des linear-gradient(). Wir wollen, dass unsere bar zu dehnen, über die .wrap-element, so dass der horizontale Abstand ist immer noch 50%, aber vertikal, wie ein schmaler Strich. Wir wollen noch das Diagramm groß genug, um passen Sie den Wert label. Dies bedeutet, dass wir gehen mit kleineren vertikalen Polsterung. Wir werden auch eine Verringerung der border-radius, da nur 50% geben würde, uns eine ellipse und das, was wir brauchen, ist ein Rechteck mit leicht abgerundeten Ecken.

Fallback ersetzen auch konischen-gradient() mit einem von-Links-nach-rechts-linear-gradient(). Da sowohl in der linear-gradient() erstellen von fallback-bar-chart und der Kegelschnitt-gradient() die Erstellung des Kreisdiagramms verwenden das gleiche stop-Liste, können wir speichern es in eine CSS-Variablen (–stop-Liste), so dass wir nicht einmal die haben es wiederholt in die kompilierte CSS-Dateien.

Schließlich wollen wir den Wert “label” in der Mitte der Leiste für den fallback, da wir nicht die Kreissegmente mehr. Das heißt, wir speichern alle post-Zentrierung Positionierung in einem CSS-Variablen (–pos), deren Wert nichts in die kein Kegelschnitt-gradient () – support-Fall und die Transformations-Kette:

.pie {
padding: 1.5 em 50%;
border-radius: 5px;
–stop-Liste: #ab3e5b calc(var(–p)*1%), #ef746f 0%;
background: linear-gradient(90deg, var(–stop-Liste));
/* das gleiche wie vorher */

&:after {
transform: translate(-50%, -50%) var(–pos, #{unquote(‘ ‘)});
/* das gleiche wie vorher */
}

@unterstützt (hintergrund: Kegelschnitt-gradient(tan, red)) {
Polsterung: 50%;
border-radius: 50%;
hintergrund: Kegelschnitt-gradient(var(–stop-Liste));
–a: calc(.5*var(–p)/100*1turn – 90deg);
–pos: drehen(var (–))
übersetzen(#{.25*$d})
drehen(calc(-1*var (–)));
}
}
}

Wir schalten um mit Hilfe eines flexbox-layout auf den Körper (da, wie clever, wie es sein kann, das Netz ist versaut Rand).

body {
display: flex;
align-items: center;
justify-content: center;
/* das gleiche wie vorher */
}

Dies alles gibt uns ein Balkendiagramm fallback für Browser, die keine Unterstützung Kegelschnitt-gradient().

Der fallback für das Finale Kreisdiagramm Ergebnis (live-demo).

Responsifying es alle

Das einzige problem, das wir noch haben ist, dass, wenn der viewport schmaler ist als die Torte, Durchmesser, Dinge, die sehen nicht mehr so gut.

CSS-Variablen und media queries ist die Rettung!

Wir den Durchmesser auf eine CSS-Variablen (–d) wird verwendet, um die Torte Abmessungen und die position des Werte-label in der Mitte unserer Scheibe.

.wrap {
–d: #{$d};
Breite: var(–d);
/* das gleiche wie vorher */

@media (max-width: $d) { –d: 95vw }
}

Unter bestimmten viewport-Breite, wir verringern auch die font-size, die margin für unsere <label> – Elemente, und wir nicht position den Wert “label” in der Mitte der dunkle Kreissegment nicht mehr, aber eher in der Mitte der Torte selbst:

.wrap {
/* das gleiche wie vorher */

@media (max-width: 265px) { schriftart-Größe: .75em; }
}

[type=’radio’] {
/* das gleiche wie vorher */

+ label {
/* das gleiche wie vorher */

@media (max-width: 195px) { margin-top: .25em; }
}
}

.pie{
/* das gleiche wie vorher */

@media (max-width: 160px) { –pos: #{unquote(‘ ‘)} }
}

Dies gibt uns unser abschließendes Resultat: eine ansprechende Kreisdiagramm in Browsern Unterstützung Kegelschnitt-gradient() nativ. Und, auch wenn das leider nur Blink-Browser mit der Experimental-Web-Platform-features flag für jetzt haben wir eine solide fallback macht, dass eine ansprechende bar-chart für alle anderen Browser. Wir auch die Animation zwischen Werten—das ist wieder nur Blinken Browser mit der Experimental-Web-Platform-features flag an dieser Stelle.

Finden Sie den Stift, indem Sie thebabydino (@thebabydino) auf CodePen.

Bonus: radial Fortschritt!

Wir können auch für dieses Konzept zu bauen, einen radialen Fortschrittsanzeige, wie das unten (inspiriert durch das Stift):

Die radiale Fortschritt und den fallback.

Die Technik ist so ziemlich das gleiche, außer wir lassen den Wert “label” tot in der Mitte und legen Sie das Kegelschnitt-gradient (): before pseudo-element. Das ist, weil wir eine Maske verwenden, um loszuwerden, alles, außer für eine dünne äußere ring, und wenn wir wurden, um die konisch-gradient() und die Maske auf das element selbst, die Maske dann würde auch ausblenden, die Wert label auf der Innenseite, und wir wollen, dass sichtbar.

Klick auf den <button> einen neuen Wert für unsere unitless Prozentsatz (–p) ist zufällig generiert und wir den übergang reibungslos zwischen den Werten. Einen festen übergang-Dauer schaffen würde ein ganz langsamer übergang zwischen zwei Werten (z.B. 47% auf 49%) und einen sehr schnellen übergang beim Wechsel zwischen Werte mit einem größeren Abstand zwischen (z.B. 3% auf 98%). Wir umgehen dies, indem man die transition-Dauer hängt vom absoluten Wert der Differenz zwischen dem vorherigen Wert der –p und die neu erstellten Wert.

[id=’out’] { /* radial-progress-element */
übergang: –p-calc(var(–dp, 1)*1s) ease-out;
}
const _GEN = document.getElementById(‘gen’),
_OUT = document.getElementById(‘out’);

_GEN.addEventListener(‘click’, e => {
lassen Sie old_perc = ~~_OUT.Stil.getPropertyValue(‘–p’),
new_perc = Math.round(100*Math.random());

_OUT.Stil.setProperty(‘–p’, new_perc);
_OUT.Stil.setProperty(‘–dp’, .01*Math.abs(old_perc – new_perc));
_OUT.setAttribute(‘aria-label’, `Graphische Darstellung der generierten Prozentsatz: ${new_perc}% von 100%.`)
}, false);

Dies gibt uns eine schöne animierte radial-Statusanzeige für die Browser unterstützen alle die neuen und glänzenden Eigenschaften. Wir haben eine lineare fallback für andere Browser:

Finden Sie den Stift, indem Sie thebabydino (@thebabydino) auf CodePen.