Morsomt med lerret Lag et bargrafikkplugin, del 2

I denne todelte serien kombinerer vi det allsidige lerretelementet med det robuste jQuery-biblioteket for å lage et bargrafikk-plugin. I denne andre delen skal vi konvertere den til et jQuery-plugin, og deretter legge til noe øyeffel og flere funksjoner.

Avslutte Morsomt med lerret todelte serier, i dag skal vi lage en stolpediagramplugg; ikke en vanlig plugin, husk deg. Vi skal vise noe jQuery-kjærlighet til lerretelementet for å lage et meget robust plugin.

I del ett så vi utelukkende på å implementere logikken til pluggen som et frittstående skript. På slutten av del 1 så barlinjediagrammet slik ut.


Resultat på slutten av del 1

I denne siste delen vil vi jobbe med å konvertere koden vår og gjøre den til en riktig jQuery-plugin, legge til noen visuelle niceties og til slutt med noen ekstra funksjoner. Til slutt ser utgangen vår ut som:


Ferdig produkt

Alle oppvarmet? Lar deg dykke inn!


Plugin Formalities

Før vi begynner å konvertere koden til et plugin, må vi først se på noen formaliteter når det gjelder plugin authoring.


Navngi plugin

Vi begynner med å velge et navn for plugin. Jeg har valgt søylediagram og omdøpt JavaScript-filen til jquery.barGraph.js. Vi vedlegger nå all koden fra forrige artikkel i følgende utdrag.

 $ .fn.barGraph = funksjon (innstillinger) // kode her

innstillinger inneholder alle valgfrie parametrene som sendes til plugin.


Fungerer rundt $ Symbol-problemet

I jQuery plugin authoring, vurderer det generelt en god praksis å bruke jQuery i stedet for $ alias i koden din, for å minimere konflikter med andre Javascript-biblioteker. I stedet for å gå gjennom alle de problemene, kan vi bare bruke tilpassede aliaser som nevnt i jQuery-dokumentene. Vi vedlegger all vår plugin kode i denne selvutførende anonym funksjonen, som vist nedenfor:

 (funksjon ($) $ .fn.barGraph = funksjon (innstillinger) // plugin implementeringskode her) (jQuery);

I hovedsak innkapsler vi all vår kode i en funksjon og sender jQuery til den. Vi kan gratis bruke $ alias så mye som vi vil inne i koden vår nå, uten å måtte bekymre oss for at det kan være i konflikt med andre JavaScript-biblioteker.


Standardene

Ved utforming av et plugin er det god forstand å avsløre et rimelig antall innstillinger til brukeren, mens du bruker fornuftige standardalternativer hvis brukerne bruker pluginet uten å overføre noen alternativer til det. Med det for øye skal vi gjøre det mulig for brukeren å endre hver av variablene i grafalternativene jeg nevnte i denne forrige artikkelen i denne serien. Å gjøre det er enkelt; vi definerer bare hver av disse variablene som egenskapene til et objekt, og deretter får du tilgang til dem.

 var standard = barSpacing = 20, barWidth = 20, cvHeight = 220, numYlabels = 8, xOffset = 20, maxVal, gWidth = 550, gHeight = 200; ;

Vi må endelig slå sammen standardvalgene med de bestått alternativene, og gi preferanse til de bestått. Denne linjen tar vare på det.

 var alternativ = $ .extend (standard, innstillinger);

Husk å endre navnene på variablene når det er nødvendig. Som i -

 retur (param * barWidth) + ((param + 1) * barSpacing) + xOffset;

... endres til:

 returnere (param * option.barWidth) + ((param + 1) * option.barSpacing) + option.xOffset;

refactoring

Det er her pluggen er hamret ut. Vår gamle implementering kan bare produsere en enkelt graf på en side, og muligheten til å lage flere grafer på en side er hovedårsaken til at vi lager et plugin for denne funksjonaliteten. I tillegg må vi sørge for at brukeren ikke trenger å lage et lerretelement for hver graf som skal opprettes. Med det i tankene skal vi lage lerretene dynamisk etter behov. La oss fortsette. Vi ser på tidligere og oppdaterte versjoner av de relevante delene av koden.


Påkoble pluginprogrammet

Før vi begynner, vil jeg gjerne påpeke hvordan vårt plugin vil bli påkalt.

 $ ("# år"). barGraph (barSpacing = 30, barWidth = 25, numYlabels = 12,);

Så enkelt som det. år er ID-en av tabellen som holder alle verdiene våre. Vi sender inn alternativene etter behov.


Skaffe dataSource

For å starte ting, trenger vi først en referanse til datakilden for grafene. Vi har nå tilgang til kildeelementet og får sin ID. Legg til følgende linje i massevis av grafvariabler som vi erklærte tidligere.

 var dataSource = $ (dette) .attr ("id");

Vi definerer en ny variabel og tilordner den verdien av det bestått elementets ID-attributt. Innenfor vår kode, dette refererer til det valgte DOM-elementet. I vårt eksempel refererer det til bordet med en ID på år.

I den forrige implementeringen ble ID-kilden hardkodet inn. Nå erstatter vi det med ID-attributtet vi hentet tidligere. Den tidligere versjonen av grabValues funksjonen er under:

 funksjon grabValues ​​() // Få tilgang til den nødvendige tabellcellen, trekk ut og legg til verdien til verdiene array. $ ("# data tr td: nth-child (2)"). hver (funksjon () gValues.push ($ (dette) .text ());); // Få tilgang til den nødvendige tabellcellen, utdrag og legg til verdien til xLabels-arrayet. $ ("# data tr td: nth-barn (1)"). hver (funksjon () xLabels.push ($ (dette) .text ());); 

Det er oppdatert til dette:

 funksjon grabValues ​​() // Få tilgang til den nødvendige tabellcellen, trekk ut og legg til verdien til verdiene array. $ ("#" + dataSource + "tr td: nth-child (2)") hver (funksjon () gValues.push ($ (dette) .text ());); // Få tilgang til den nødvendige tabellcellen, utdrag og legg til verdien til xLabels-arrayet. $ ("#" + dataSource + "tr td: nth-child (1)"). hver (funksjon () xLabels.push ($ (dette) .text ());); 

Injiserer lærredelementet

 funksjon initCanvas () $ ("#" + dataSource) .after (" "); // Prøv å få tilgang til lerretelementet cv = $ (" # bargraph - "+ dataSource) .get (0); if (! Cv.getContext) return; // Prøv å få en 2D-kontekst for lerret og kaste en feil hvis det ikke er mulig å ctx = cv.getContext ('2d'); hvis (! ctx) return;

Vi lager et lerretelement og injiserer det i DOM etter bordet, som fungerer som datakilde. jQuery etter funksjonen kommer veldig praktisk her. En klasseattributt av søylediagram og et ID-attributt i formatet stolpediagram-dataSourceID brukes også for å gjøre det mulig for brukeren å utforme dem alle som en gruppe eller individuelt etter behov.


Sykling gjennom passerte elementer

Det er to måter du kan påkalle dette pluginet faktisk. Du kan enten opprette hver graf separat, bare passere inn i én datakilde, eller du kan bestå i en rekke kilder. I sistnevnte tilfelle vil vår nåværende konstruksjon støte på en feil og avslutte. For å rette opp dette bruker vi Hver konstruere for å iterere over settet av bestått elementer.

 (funksjon ($) $ .fn.barGraph = funksjon (innstillinger) // Alternativvariabler var standard = // alternativer her; // Slå sammen de bestått parametrene med standardene var alternativ = $ .extend (standardinnstillinger, innstillinger ); // Syklus gjennom hvert passert objekt this.each (function () // Implementation code here); // Returnerer jQuery-objektet for å tillate kjedbarhet. Returner dette;) (jQuery);

Vi inkapslerer all kode etter at du har fått og slått sammen innstillingene i this.each konstruere. Vi sørger også for å returnere jQuery-objektet på slutten for å aktivere kjørbarhet.

Med dette er vår refactoring ferdig. Vi bør kunne påberope vårt plugin og lage så mange grafer som nødvendig.


Legge til øye Candy

Nå som vår konvertering er fullført, kan vi jobbe med å gjøre det visuelt bedre. Vi skal gjøre en rekke ting her. Vi ser på hver av dem separat.


temaer

Den eldre versjonen brukte en tørr grå for å tegne grafene. Vi skal implementere en temmekanisme for stolpene nå. Dette består av seg selv av en rekke trinn.


Ocean: Standardtemaet
løvverk
Kirsebærblomst
Spectrum

Legge til det i alternativene

 var standard = // Andre standardinnstillinger her tema: "Ocean",;

Vi legger til en tema alternativ til standardinnstillingene slik at brukeren kan endre temaet til en av de fire forhåndsinnstillingene som er tilgjengelige.

Angi det valgte utvalget

 funksjon grabValues ​​() // Tidligere kodebryter (option.theme) case 'Ocean': gTheme = thBlue; gå i stykker; tilfelle 'Løvverk': gTheme = thGreen; gå i stykker; sak 'Cherry Blossom': gTheme = thPink; gå i stykker; tilfelle 'Spektrum': gTheme = thAssorted; gå i stykker; 

En enkel bytte om konstruere ser på option.theme setter inn og peker på gTheme variabel til nødvendige farger. Vi bruker beskrivende navn for temaene i stedet for generiske.

Definere Farger Array

 // Temaer var thPink = ['#FFCCCC', '# FFCCCC', '# FFC0C0', '# FFB5B5', '# FFADAD', '# FFA4A4', '# FF9A9A', '# FF8989', '# FF6D6D ']; var thBlue = ['# ACE0FF', '# 9CDAFF', '# 90D6FF', '# 86D2FF', '# 7FCFFF', '# 79CDFF', '# 72CAFF', '# 6CC8FF', '# 57C0FF']; var THGreen = ['# D1FFA6', '# C6FF91', '# C0FF86', '# BCFF7D', '# B6FF72', '# B2FF6B', '# AAFE5D', '# A5FF51', '# 9FFF46']; var thAssorted = ['# FF93C2', '# F193FF', '# B893FF', '# 93A0FF', '# 93D7FF', '# 93F6FF', '# ABFF93', '# FF9B93'];

Deretter definerer vi en rekke arrays, hver med en rekke nyanser av en spesiell farge. De starter med lysere fargetone og fortsetter å øke. Vi slår gjennom disse arrays senere. Å legge til temaer er like enkelt som å legge til en matrise for den spesifikke farge du trenger, og deretter endre den tidligere bytte om for å gjenspeile endringene.

Hjelperfunksjonen

 funksjon getColour (param) return Math.ceil (Math.abs (((gValues.length / 2) -param))); 

Dette er en liten funksjon som lar oss oppnå og bruke en gradient som effekt på grafene. I hovedsak beregner vi den absolutte forskjellen mellom halvparten av antall verdier som skal gjengis og den overførte parameteren, som er indeksen for det valgte elementet i matrisen. På den måten kan vi skape en jevn gradient. Siden vi bare har definert ni farger i hver av fargearkene, er vi begrenset til atten verdier en graf. Utvide dette nummeret bør være ganske trivielt.

Innstilling av fyllstil

 funksjon drawGraph () for (index = 0; index 

Det er her vi faktisk grafer grafer. I stedet for å sette en statisk verdi til fyllstil eiendom, bruker vi getColour funksjon for å hente den nødvendige indeksen for elementet i det valgte temaets array.


opacity

Neste opp, vi skal gi brukeren muligheten til å kontrollere opaciteten til stolpene som er tegnet. Innstillinger Dette er en to-trinns prosess.


Uten gjennomsiktighet
Med en verdi på 0,8

Legge til det i alternativene

 var standard = // Andre standardinnstillinger her barOpacity: 0,8,;

Vi legger til en barOpacity alternativet til standardene, slik at brukeren kan endre uklarheten til grafene til en verdi fra 0 til 1, der 0 er helt gjennomsiktig og 1 er helt uigennemsiktig.

Innstilling av globalAlpha

 funksjon drawGraph () for (index = 0; index 

De globalAlpha Egenskapen kontrollerer ugjennomtrengelighet eller gjennomsiktighet av det gjengitte elementet. Vi setter denne egenskapens verdi til den bestått verdien eller standardverdien for å legge til litt gjennomsiktighet. Som en fornuftig standard bruker vi en verdi på 0,8 for å gjøre det bare en liten bit gjennomsiktig.


Nett

Et rutenett kan være ekstremt nyttig for å behandle dataene som presenteres i en graf. Selv om jeg i utgangspunktet ønsket et riktig rutenett, bestemte jeg seg senere for en serie horisontale linjer som klarte seg med Y-akselettene og helt kastet de vertikale linjene, siden de bare kom i veien for dataene. Med det ut av veien, la oss nå implementere en måte å gjøre det på.


Med grid deaktivert
Med grid aktivert

Opprette linjene ved hjelp av stier og lineTo Metoden syntes å være den mest åpenbare løsningen for å tegne grafene, men jeg skjedde å løpe inn i en gjengivelsesfeil som gjorde denne tilnærmingen uegnet. Derfor stikker jeg med fillRect metode for å lage disse linjene også. Her er funksjonen i sin helhet.

 funksjon drawGrid () for (index = 0; index 

Dette ligner veldig på å tegne Y-akseetikettene, bortsett fra at i stedet for å skape en etikett tegner vi en horisontal linje som strekker seg over bredden på grafen med en bredde på 1 px. De y funksjonen hjelper oss i posisjoneringen.

Legge til det i alternativene

 var standard = // Andre standardinnstillinger her disableGrid: false,;

Vi legger til en disableGrid alternativet til standardene, slik at brukeren kan kontrollere om et rutenett blir gjengitt eller ikke. Som standard blir det gjengitt.

 // Funksjon samtaler hvis (! Option.disableGrid) drawGrid (); 

Vi kontrollerer bare om brukeren vil at nettverket skal gjengis og fortsette tilsvarende.


Outlines

Nå som stolpene alle er farget, mangler det aksent mot en lysere bakgrunn. For å rette opp dette, trenger vi en 1 px slag. Det er to måter å gjøre dette på. Den første og enkleste måten ville være å bare legge til en strokeRect metode til DrawGraph metode; eller vi kunne bruke lineTo metode for raskt å berre rektanglene. Jeg valgte den tidligere ruten siden som før lineTo Metoden kastet litt merkelig gjengivelse på meg.


Uten strekk
Med strekk

Legge til det i Alternativer

Først legger vi det til mislighold Motta å gi brukeren kontroll over om dette er brukt eller ikke.

 var standard = // Andre standardinnstillinger her showOutline: true,;
 funksjon drawGraph () // Forrige kode hvis (option.showOutline) ctx.fillStyle = "# 000"; ctx.strokeRect (x (index), y (gValues ​​[index]), bredde (), høyde (gValues ​​[index]));  // Resten av koden

Vi kontrollerer om brukeren vil gjengi konturene, og hvis ja, fortsetter vi. Dette er nesten det samme som gjengivelse av de faktiske stolpene bortsett fra at i stedet for å bruke fillRect metode vi bruker strokeRect metode.


Skyggelegging

I den opprinnelige implementeringen er det ingen forskjell mellom selve lerretelementet og selve gjengivelsen av stolpene. Vi løser dette nå.


Uten skygge
Med skyggelegging
 funksjon shadeGraphArea () ctx.fillStyle = "# F2F2F2"; ctx.fillRect (option.xOffset, 0, gWidth-option.xOffset, gHeight); 

Dette er en liten funksjon som nyanser det nødvendige området. Vi dekker lerretelementet minus området dekket av etikettene på begge aksene. De to første parametrene peker på startpunktets x- og y-koordinater, og de to siste punktene peker på ønsket bredde og høyde. Ved å starte på option.offset, Vi eliminerer området som dekkes av Y-akselettene, og ved å begrense høyden til gHeight, Vi eliminerer X-aksens etiketter.


Legge til funksjoner

Nå som grafen ser bra ut, kan vi konsentrere oss om å legge til noen nye funksjoner i vårt plugin. Vi ser på hver for seg.

Tenk på denne grafen av de berømte 8K-toppene.

Når den høyeste verdien er tilstrekkelig høy nok, og de fleste verdiene faller innenfor 10% av maksimumverdien, slutter grafen å være nyttig. Vi har to måter å rette opp på dette.


ShowValue

Vi skal begynne med den enklere løsningen først. Ved å gjengjøre verdien av de respektive grafene øverst, er problemet nesten løst siden de individuelle verdiene enkelt kan differensieres. Slik er det implementert.

 var standard = // Andre standardinnstillinger her showValue: true,;

Først legger vi til en oppføring til mislighold objekt for å gjøre det mulig for brukeren å slå den på og av etter vilje.

 // Funksjonen ringer om (option.showValue) drawValue (); 

Vi kontrollerer om brukeren vil at verdien skal vises og fortsette tilsvarende.

 funksjon drawValue () for (index = 0; index 

Vi lurer gjennom gValues array og gjengiv hver verdi individuelt. Beregningene involverer valAsString og valX er bare små beregninger for å hjelpe oss i de riktige innrykkene, så det ser ikke ut på plass.


Scale

Dette er vanskeligere av de to løsningene. I denne metoden, i stedet for å starte Y-akseetikettene på 0, starter vi mye nærmere minimumsverdien. Jeg skal forklare når vi går. Vær oppmerksom på at forskjellen mellom etterfølgende verdier i forhold til maksimumsverdien i det ovennevnte eksemplet er ganske ubetydelig og ikke viser effektiviteten like mye. Andre datasett bør gjøre det enklere å analysere resultatene.

Legge til det i Alternativer

 var standard = // Andre standardinnstillinger her skala: false;

Oppdaterer skaleringsfunksjonen

Siden skala funksjon er en integrert del av renderingsprosessen, vi må oppdatere den for å tillate skaleringsfunksjonen. Vi oppdaterer det slik:

 funksjonsskala (param) return ((option.scale)? Math.round ((param-minVal) / (maxVal-minVal)) * gHeight): Math.round ((param / maxVal) * gHeight)); 

Jeg vet at dette ser litt komplisert ut, men det ser bare ut som følge av bruk av den ternære betingede operatøren. I hovedsak kontrollerer vi verdien av option.scale og hvis det står feil, blir den eldre koden utført. Hvis det er sant, i stedet for å normalisere verdien som en funksjon av maksimalverdien i arrayet, normaliserer vi nå det for å være en funksjon av forskjellen mellom maksimum og minimumsverdier. Som bringer oss til:

Oppdaterer maxValues Funksjon

Vi må nå finne ut både maksimums- og minimumsverdien, i motsetning til bare det maksimale vi måtte gjøre før. Funksjonen er oppdatert til dette:

 funksjon minmaxValues ​​(arr) maxVal = 0; for (i = 0; iparseInt (arr [i])) minVal = parseInt (arr [i]);  maxVal * = 1,1; minVal = minVal - Math.round ((maxVal / 10)); 

Jeg er sikker på at du kunne oppnå det samme i en enkelt sløyfe uten å bruke så mange linjer med kode som meg, men jeg følte meg spesielt uncreative på den tiden, så bære med meg. Med beregningsformaliteter ut av veien, gir vi en 5% økning til Maxval variabel og til minverdi variabel, trekker vi en verdi lik 5% av Maxval s verdi. Dette er for å sikre at stolpene ikke berører toppen hver gang, og forskjellene mellom hver Y-akse-etikett er jevn.

Oppdaterer drawYlabels Funksjon

Med all grunnarbeidet gjort, fortsetter vi nå med å oppdatere Y-aksen etikett gjengivelse rutine for å reflektere skaleringen.

 funksjon drawYlabels () ctx.save (); for (indeks = 0; indeks 

Ganske kjedelig oppdatering hvis du spør meg! Kjernen i funksjonen forblir den samme. Vi kontrollerer bare om brukeren har aktivert skalering og avgren av koden etter behov. Hvis aktivert, endrer vi måten Y-merkene er tildelt for å sikre at de overholder den nye algoritmen. I stedet for maksimalverdien fordelt på n antall like jevnt fordelte tall beregner vi nå forskjellen mellom maksimum og minimumsverdi, fordeler den til jevnt fordelte tall, og legger den til minimumsverdien for å bygge vårt utvalg av Y-akseletter. Etter dette fortsetter vi som normalt, og gir hver etikett hver for seg. Siden vi gjorde bunnen mest 0 manuelt, må vi sjekke om skalering er aktivert og deretter gjengi minimumsverdien på plass. Ikke vær oppmerksom på de små, numeriske tilleggene til hver passerte parameter; det er bare for å sikre at hvert element av graflinjene er som forventet.


Dynamisk resizing

I vår tidligere implementering har vi hardt kodet dimensjonene på grafen, noe som gir betydelige vanskeligheter når antall verdier endres. Vi skal rette opp dette nå.

Legge til det i alternativene

 var standard = // Andre standardinnstillinger her cvHeight: 250, // I px;

Vi lar brukeren sette høyde på lerretelementet alene. Alle andre verdier beregnes dynamisk og brukes etter behov.

Oppdaterer initCanvas Funksjon

De initCanvas funksjonen håndterer alt lærredsinitialisering, og må derfor oppdateres for å implementere den nye funksjonaliteten.

 funksjon initCanvas () $ ("#" + dataSource) .after (" "); // Prøv å få tilgang til lerretelementet cv = $ (" # bargraph - "+ dataSource) .get (0); cv.width = gValues.length * (option.barSpacing + option.barWidth) + option.xOffset + option.barSpacing; cv.height = option.cvHeight; gWidth = cv.width; gHeight = option.cvHeight-20; hvis (! cv.getContext) return; // Prøv å få en 2D-kontekst for lerretet og kaste en feil hvis ikke ctx = cv.getContext ('2d'); hvis (! ctx) return;

Etter at du har injisert lerretelementet, får vi en referanse til det opprettede elementet. Lerretselementets bredde beregnes som en funksjon av antall elementer i arrayet - gValues , mellomrom mellom hver stolpe - option.barSpacing, bredden på hver stolpe selv - option.barWidth og endelig option.xOffset. Grafens bredde endres dynamisk basert på hver av disse parametrene. Høyden er brukermodifiserbar og standard til 220px med gjengivelsesområdet for linjen i seg selv er 220px. 20px er allokert til X-aksen etikettene.


Gjemmer kilden

Det er fornuftig at brukeren ønsker å skjule kildetabellen når grafen er opprettet. Med dette i bakhodet lar vi brukeren avgjøre om bordet skal fjernes eller ikke.

 var standard = // Andre standardinnstillinger her hideDataSource: true,;
 hvis (option.hideDataSource) $ ("#" + dataSource) .remove ();

Vi sjekker om brukeren vil gjemme bordet, og hvis ja, fjerner vi det helt fra DOM ved hjelp av jQuery fjerne metode.


Optimalisering av vår kode

Nå som alt hardt arbeid har blitt gjort, kan vi se hvordan du optimaliserer koden vår. Siden denne koden er skrevet helt for undervisning, har det meste av arbeidet blitt innkapslet som separate funksjoner, og i tillegg er de mye mer verbose enn de trenger å være.

Hvis du virkelig vil ha den skarpeste koden mulig, kan hele pluginet, unntatt initialisering og beregning, skrives om innen to looper. En looping gjennom gValues array for å tegne barer selv og X-aksen etiketter; og den andre sløyfen detererer fra 0 til numYlabels å gjengi rutenettet og Y-aksen etikettene. Koden ville se mye messier, men det burde føre til en betydelig mindre kodebase.


Sammendrag

Det er det folkens! Vi har opprettet et høyt nivå plugin helt fra begynnelsen. Vi så på en rekke emner i denne serien, inkludert:

  • Ser på lerretelementets renderingskjema.
  • Noen av lerretelementets gjengivelsesmetoder.
  • Normalisere verdier som gjør at vi kan uttrykke det som en funksjon av en annen verdi.
  • Noen nyttige datautvinningsteknikker bruker jQuery.
  • Kjernelogikken for å gjengi grafen.
  • Konvertere skriptet til et fullverdig jQuery-plugin.
  • Hvordan forbedre det visuelt og utvide det enda mer funksjonelt.

Jeg håper du har hatt så mye moro å lese dette som jeg hadde skrevet det. Dette er et 270-merket linjearbeid, jeg er sikker på at jeg har sluppet noe ut. Ta gjerne på kommentarene og spør meg. Eller kritiser meg. Eller ros meg. Du vet, det er ditt anrop! Glad koding!