Güncellenebilir D3.js Grafiklerine Doğru
Yayınlanan: 2022-03-11Tanıtım
D3.js, Mike Bostock tarafından geliştirilen veri görselleştirmeleri için açık kaynaklı bir kitaplıktır. D3, veriye dayalı belgeler anlamına gelir ve adından da anlaşılacağı gibi kitaplık, geliştiricilerin verilere dayalı olarak DOM öğelerini kolayca oluşturmasına ve değiştirmesine olanak tanır. Kitaplığın yetenekleriyle sınırlı olmamakla birlikte, D3.js tipik olarak SVG öğeleriyle birlikte kullanılır ve sıfırdan vektör veri görselleştirmeleri geliştirmek için güçlü araçlar sunar.
Basit bir örnekle başlayalım. 5 binlik bir yarış için antrenman yaptığınızı ve geçen haftanın her günü koştuğunuz mil sayısının yatay bir çubuk grafiğini yapmak istediğinizi varsayalım:
var milesRun = [2, 5, 4, 1, 2, 6, 5]; d3.select('body').append('svg') .attr('height', 300) .attr('width', 800) .selectAll('rect') .data(milesRun) .enter() .append('rect') .attr('y', function (d, i) { return i * 40 }) .attr('height', 35) .attr('x', 0) .attr('width', function (d) { return d*100}) .style('fill', 'steelblue');
Eylem halinde görmek için bl.ocks.org'da kontrol edin.
Bu kod tanıdık geliyorsa, bu harika. Değilse, Scott Murray'in eğitimlerini D3.js'ye başlamak için mükemmel bir kaynak olarak buldum.
D3.js ile geliştirme yapmak için yüzlerce saat çalışmış bir serbest çalışan olarak, geliştirme modelim bir evrim geçirdi ve her zaman en kapsamlı müşteri ve kullanıcı deneyimlerini yaratma nihai hedefi oldu. Daha sonra daha ayrıntılı olarak tartışacağım gibi, Mike Bostock'un yeniden kullanılabilir çizelgeler modeli, aynı çizelgeyi herhangi bir sayıda seçimde uygulamak için denenmiş ve doğru bir yöntem sundu. Ancak, grafik başlatıldığında sınırlamaları fark edilir. Bu yöntemle D3'ün geçişlerini ve güncelleme modellerini kullanmak isteseydim, verilerdeki değişikliklerin tamamen grafiğin oluşturulduğu kapsamda ele alınması gerekiyordu. Pratikte bu, filtrelerin, açılır seçimlerin, kaydırıcıların ve yeniden boyutlandırma seçeneklerinin tümünün aynı işlev kapsamında uygulanması anlamına geliyordu.
Bu sınırlamaları tekrar tekrar deneyimledikten sonra, D3.js'nin tüm gücünden yararlanmanın bir yolunu yaratmak istedim. Örneğin, tamamen ayrı bir bileşenin açılır listesindeki değişiklikleri dinlemek ve eski verilerden yeniye sorunsuz bir şekilde grafik güncellemelerini tetiklemek. Grafik kontrollerini tam işlevsellik ile teslim edebilmek ve bunu mantıklı ve modüler bir şekilde yapabilmek istedim. Sonuç, güncellenebilir bir çizelge modelidir ve ben bu modeli oluşturmaya yönelik tüm ilerlememi gözden geçireceğim.
D3.js Grafikleri Örüntü İlerlemesi
Adım 1: Yapılandırma Değişkenleri
Görselleştirmeler geliştirmek için D3.js'yi kullanmaya başladığımda, bir grafiğin özelliklerini hızlı bir şekilde tanımlamak ve değiştirmek için yapılandırma değişkenlerini kullanmak çok uygun hale geldi. Bu, çizelgelerimin tüm farklı uzunluk ve veri değerlerini işlemesine izin verdi. Mil koşusunu görüntüleyen aynı kod parçası artık herhangi bir aksaklık olmadan daha uzun bir sıcaklık listesi görüntüleyebilir:
var highTemperatures = [77, 71, 82, 87, 84, 78, 80, 84, 86, 72, 71, 68]; var height = 300; var width = 800; var barPadding = 1; var barSpacing = height / highTemperatures.length; var barHeight = barSpacing - barPadding; var maxValue = d3.max(highTemperatures); var widthScale = width / maxValue; d3.select('body').append('svg') .attr('height', height) .attr('width', width) .selectAll('rect') .data(highTemperatures) .enter() .append('rect') .attr('y', function (d, i) { return i * barSpacing }) .attr('height', barHeight) .attr('x', 0) .attr('width', function (d) { return d*widthScale}) .style('fill', 'steelblue');
Eylem halinde görmek için bl.ocks.org'da kontrol edin.
Çubukların yüksekliklerinin ve genişliklerinin, verilerin hem boyutuna hem de değerlerine göre nasıl ölçeklendirildiğine dikkat edin. Bir değişken değiştirilir ve gerisi halledilir.
Adım 2: Fonksiyonlar Aracılığıyla Kolay Tekrarlama
İş mantığının bir kısmını soyutlayarak, genelleştirilmiş bir veri şablonunu işlemeye hazır olan daha çok yönlü kod oluşturabiliyoruz. Sonraki adım, bu kodu, başlatmayı yalnızca bir satıra indiren bir oluşturma işlevine sarmaktır. İşlev üç bağımsız değişken alır: veriler, bir DOM hedefi ve varsayılan yapılandırma değişkenlerinin üzerine yazmak için kullanılabilecek bir seçenekler nesnesi. Bunun nasıl yapılabileceğine bir göz atın:
var milesRun = [2, 5, 4, 1, 2, 6, 5]; var highTemperatures = [77, 71, 82, 87, 84, 78, 80, 84, 86, 72, 71, 68, 75, 73, 80, 85, 86, 80]; function drawChart(dom, data, options) { var width = options.width || 800; var height = options.height || 200; var barPadding = options.barPadding || 1; var fillColor = options.fillColor || 'steelblue'; var barSpacing = height / data.length; var barHeight = barSpacing - barPadding; var maxValue = d3.max(data); var widthScale = width / maxValue; d3.select(dom).append('svg') .attr('height', height) .attr('width', width) .selectAll('rect') .data(data) .enter() .append('rect') .attr('y', function (d, i) { return i * barSpacing }) .attr('height', barHeight) .attr('x', 0) .attr('width', function (d) { return d*widthScale}) .style('fill', fillColor); } var weatherOptions = {fillColor: 'coral'}; drawChart('#weatherHistory', highTemperatures, weatherOptions); var runningOptions = {barPadding: 2}; drawChart('#runningHistory', milesRun, runningOptions);
Eylem halinde görmek için bl.ocks.org'da kontrol edin.
Bu bağlamda D3.js seçimleri hakkında da not almak önemlidir. d3.selectAll('rect')
gibi genel seçimlerden her zaman kaçınılmalıdır. Sayfada başka bir yerde SVG'ler varsa, sayfadaki tüm rect
seçimin bir parçası olur. Bunun yerine, iletilen DOM başvurusunu kullanarak, öğeleri eklerken ve güncellerken başvurabileceğiniz bir svg
nesnesi oluşturun. Bu teknik, aynı zamanda, bir referans benzeri çubuklar kullanmak, D3.js seçimini tekrar yapmak zorunda kalmayı önlediği için, grafik oluşturmanın çalışma zamanını da iyileştirebilir.
Adım 3: Yöntem Zincirleme ve Seçimler
Yapılandırma nesnelerini kullanan önceki iskelet, JavaScript kitaplıklarında çok yaygın olsa da, D3.js'nin yaratıcısı Mike Bostock, yeniden kullanılabilir grafikler oluşturmak için başka bir model önerir. Kısacası, Mike Bostock, çizelgelerin alıcı ayarlayıcı yöntemlerle kapanışlar olarak uygulanmasını önerir. Grafik uygulamasına biraz karmaşıklık eklerken, yapılandırma seçeneklerinin ayarlanması, yalnızca yöntem zincirleme kullanılarak arayan için çok basit hale gelir:
// Using Mike Bostock's Towards Reusable Charts Pattern function barChart() { // All options that should be accessible to caller var width = 900; var height = 200; var barPadding = 1; var fillColor = 'steelblue'; function chart(selection){ selection.each(function (data) { var barSpacing = height / data.length; var barHeight = barSpacing - barPadding; var maxValue = d3.max(data); var widthScale = width / maxValue; d3.select(this).append('svg') .attr('height', height) .attr('width', width) .selectAll('rect') .data(data) .enter() .append('rect') .attr('y', function (d, i) { return i * barSpacing }) .attr('height', barHeight) .attr('x', 0) .attr('width', function (d) { return d*widthScale}) .style('fill', fillColor); }); } chart.width = function(value) { if (!arguments.length) return margin; width = value; return chart; }; chart.height = function(value) { if (!arguments.length) return height; height = value; return chart; }; chart.barPadding = function(value) { if (!arguments.length) return barPadding; barPadding = value; return chart; }; chart.fillColor = function(value) { if (!arguments.length) return fillColor; fillColor = value; return chart; }; return chart; } var milesRun = [2, 5, 4, 1, 2, 6, 5]; var highTemperatures = [77, 71, 82, 87, 84, 78, 80, 84, 86, 72, 71, 68, 75, 73, 80, 85, 86, 80]; var runningChart = barChart().barPadding(2); d3.select('#runningHistory') .datum(milesRun) .call(runningChart); var weatherChart = barChart().fillColor('coral'); d3.select('#weatherHistory') .datum(highTemperatures) .call(weatherChart);
Eylem halinde görmek için bl.ocks.org'da kontrol edin.
Grafik başlatma, D3.js seçimini kullanır, ilgili verileri bağlar ve DOM seçimini this
bağlam olarak oluşturucu işlevine iletir. Oluşturucu işlevi, varsayılan değişkenleri bir kapatmaya sarar ve çağıranın bunları, grafik nesnesini döndüren yapılandırma işlevleriyle yöntem zincirleme yoluyla değiştirmesine olanak tanır. Bunu yaparak, arayan kişi aynı grafiği aynı anda birden çok seçime dönüştürebilir veya aynı grafiği farklı verilerle farklı seçimlere işlemek için tek bir grafiği kullanabilir, tüm bunları yaparken de hacimli seçenekler nesnesinin etrafından geçmekten kaçınabilir.

4. Adım: Güncellenebilir Grafikler için Yeni Bir Model
Mike Bostock tarafından önerilen önceki model, çizelge geliştiricileri olarak bize jeneratör işlevi içinde çok fazla güç veriyor. Bir veri seti ve aktarılan herhangi bir zincirleme konfigürasyon verildiğinde, her şeyi oradan kontrol ederiz. Verilerin içeriden değiştirilmesi gerekiyorsa, sıfırdan yeniden çizmek yerine uygun geçişleri kullanabiliriz. Pencere yeniden boyutlandırma gibi şeyler bile, kısaltılmış metin kullanmak veya eksen etiketlerini değiştirmek gibi duyarlı özellikler oluşturarak zarif bir şekilde ele alınabilir.
Peki ya veriler, üreteç işlevinin kapsamı dışından değiştirilirse? Ya da grafiğin programlı olarak yeniden boyutlandırılması gerekiyorsa? Yeni veriler ve yeni boyut yapılandırmasıyla grafik işlevini yeniden çağırabiliriz. Her şey yeniden çizilecekti ve işte. Sorun çözüldü.
Ne yazık ki, bu çözümle ilgili bir takım sorunlar var.
Öncelikle, neredeyse kaçınılmaz olarak gereksiz başlatma hesaplaması yapıyoruz. Tek yapmamız gereken genişliği ölçeklendirmekken neden karmaşık veri manipülasyonu yapıyoruz? Bu hesaplamalar, bir grafik ilk kez başlatıldığında gerekli olabilir, ancak kesinlikle yapmamız gereken her güncellemede değil. Her programatik istek biraz değişiklik gerektirir ve geliştiriciler olarak bu değişikliklerin tam olarak ne olduğunu biliyoruz. Ne fazla ne az. Ayrıca, grafik kapsamında, değişiklikleri uygulamak için basit hale getiren, ihtiyaç duyduğumuz birçok şeye (SVG nesneleri, mevcut veri durumları ve daha fazlası) zaten erişimimiz var.
Örneğin yukarıdaki çubuk grafik örneğini alın. Genişliği güncellemek isteseydik ve bunu tüm grafiği yeniden çizerek yapsaydık, birçok gereksiz hesaplamayı tetiklerdik: maksimum veri değerini bulmak, çubuk yüksekliğini hesaplamak ve tüm bu SVG öğelerini oluşturmak. Gerçekten, width
yeni değerine atandığında, yapmamız gereken tek değişiklik şunlardır:
width = newWidth; widthScale = width / maxValue; bars.attr('width', function(d) { return d*widthScale}); svg.attr('width', width);
Ama daha da iyi olur. Artık grafiğin bir geçmişine sahip olduğumuz için, grafiklerimizi güncellemek ve onları kolayca canlandırmak için D3'ün yerleşik geçişlerini kullanabiliriz. Yukarıdaki örnekle devam edersek, width
bir geçiş eklemek, değiştirmek kadar basittir.
bars.attr('width', function(d) { return d*widthScale});
ile
bars.transition().duration(1000).attr('width', function(d) { return d*widthScale});
Daha da iyisi, bir kullanıcının yeni bir veri kümesine geçmesine izin verirsek, yeni verilere geçişler uygulamak için D3'ün güncelleme seçimlerini (giriş, güncelleme ve çıkış) kullanabiliriz. Ancak yeni verilere nasıl izin vereceğiz? Hatırlarsanız, önceki uygulamamız aşağıdaki gibi yeni bir grafik oluşturmuştu:
d3.select('#weatherHistory') .datum(highTemperatures) .call(weatherChart);
Verileri bir D3.js seçimine bağladık ve yeniden kullanılabilir grafiğimizi çağırdık. Verilerde yapılacak herhangi bir değişiklik, yeni verileri aynı seçime bağlayarak yapılmalıdır. Teorik olarak, eski modeli kullanabilir ve mevcut veriler için seçimi araştırabilir ve ardından bulgularımızı yeni verilerle güncelleyebiliriz. Bu sadece dağınık ve uygulanması karmaşık olmakla kalmaz, aynı zamanda mevcut grafiğin aynı tür ve biçimde olduğu varsayımını gerektirir.
Bunun yerine, JavaScript oluşturucu işlevinin yapısındaki bazı değişikliklerle, arayanın yöntem zincirleme yoluyla değişiklikleri harici olarak kolayca istemesini sağlayacak bir grafik oluşturabiliriz. Yapılandırma ve veriler ayarlanmadan ve ardından dokunulmadan bırakılmadan önce, arayan kişi artık grafik başlatıldıktan sonra bile böyle bir şey yapabilir:
weatherChart.width(420);
Sonuç, mevcut grafikten yeni bir genişliğe yumuşak bir geçiştir. Gereksiz hesaplamalar olmadan ve şık geçişlerle sonuç mutlu bir müşteridir.
Bu ekstra işlevsellik, geliştirici çabasında hafif bir artışla birlikte gelir. Bununla birlikte, tarihsel olarak zaman ayırmaya değer bulduğum bir çaba. İşte güncellenebilir grafiğin bir iskeleti:
function barChart() { // All options that should be accessible to caller var data = []; var width = 800; //... the rest var updateData; var updateWidth; //... the rest function chart(selection){ selection.each(function () { // //draw the chart here using data, width // updateWidth = function() { // use width to make any changes }; updateData = function() { // use D3 update pattern with data } }); } chart.data = function(value) { if (!arguments.length) return data; data = value; if (typeof updateData === 'function') updateData(); return chart; }; chart.width = function(value) { if (!arguments.length) return width; width = value; if (typeof updateWidth === 'function') updateWidth(); return chart; }; //... the rest return chart; }
Tam olarak uygulandığını görmek için bl.ocks.org adresinden kontrol edin.
Yeni yapıyı gözden geçirelim. Önceki kapatma uygulamasından en büyük değişiklik, güncelleme işlevlerinin eklenmesidir. Daha önce tartışıldığı gibi, bu işlevler, yeni verilere veya grafik yapılandırmalarına dayalı olarak gerekli değişiklikleri sorunsuz bir şekilde yapmak için D3.js geçişlerinden ve güncelleme modellerinden yararlanır. Bunları arayan kişi için erişilebilir kılmak için işlevler, grafiğe özellikler olarak eklenir. Ve bunu daha da kolaylaştırmak için, hem ilk yapılandırma hem de güncellemeler aynı işlev aracılığıyla gerçekleştirilir:
chart.width = function(value) { if (!arguments.length) return width; width = value; if (typeof updateWidth === 'function') updateWidth(); return chart; };
updateWidth
grafik başlatılıncaya kadar tanımlanmayacağını unutmayın. undefined
ise, konfigürasyon değişkeni global olarak ayarlanacak ve grafik kapanışında kullanılacaktır. Grafik işlevi çağrıldıysa, tüm geçişler, gerekli değişiklikleri yapmak için değiştirilen width
değişkenini kullanan updateWidth
işlevine teslim edilir. Bunun gibi bir şey:
updateWidth = function() { widthScale = width / maxValue; bars.transition().duration(1000).attr('width', function(d) { return d*widthScale}); svg.transition().duration(1000).attr('width', width); };
Bu yeni yapı ile, grafiğin verileri, onu bir D3.js seçimine bağlamak yerine, tıpkı diğer herhangi bir konfigürasyon değişkeni gibi yöntem zincirleme yoluyla geçirilir. Fark:
var weatherChart = barChart(); d3.select('#weatherHistory') .datum(highTemperatures) .call(weatherChart);
hangi olur:
var weatherChart = barChart().data(highTemperatures); d3.select('#weatherHistory') .call(weatherChart);
Bu yüzden bazı değişiklikler yaptık ve biraz geliştirici çabası ekledik, faydaları görelim.
Diyelim ki yeni bir özellik isteğiniz var: “Kullanıcının yüksek sıcaklıklar ve düşük sıcaklıklar arasında geçiş yapabilmesi için bir açılır menü ekleyin. Ve hazır olduğunuzda renk değişimini de yapın.” Mevcut grafiği silmek, yeni verileri bağlamak ve sıfırdan yeniden çizmek yerine artık düşük sıcaklık seçildiğinde basit bir arama yapabilirsiniz:
weatherChart.data(lowTemperatures).fillColor('blue');
ve sihrin tadını çıkarın. Yalnızca hesaplamaları kaydetmekle kalmıyoruz, aynı zamanda güncellenirken görselleştirmeye daha önce mümkün olmayan yeni bir anlama düzeyi ekliyoruz.
Burada geçişler hakkında önemli bir uyarı sözü gerekiyor. Aynı öğe üzerinde birden çok geçiş planlarken dikkatli olun. Yeni bir geçiş başlatmak, önceden çalışan geçişleri dolaylı olarak iptal edecektir. Elbette, bir D3.js tarafından başlatılan geçişte bir öğe üzerinde birden çok nitelik veya stil değiştirilebilir, ancak birden çok geçişin aynı anda tetiklendiği bazı örneklerle karşılaştım. Bu durumlarda, güncelleme işlevlerinizi oluştururken üst ve alt öğelerde eşzamanlı geçişler kullanmayı düşünün.
Felsefede Bir Değişim
Mike Bostock, grafik oluşturmayı kapsüllemenin bir yolu olarak kapanışları tanıtıyor. Onun kalıbı, birçok yerde farklı verilerle aynı grafiği oluşturmak için optimize edilmiştir. Ancak D3.js ile çalıştığım yıllarda öncelikler arasında küçük bir fark buldum. Farklı verilerle aynı görselleştirmeyi oluşturmak için bir grafiğin bir örneğini kullanmak yerine, tanıttığım yeni model, arayanın, her biri başlatmadan sonra bile tamamen değiştirilebilen birden çok grafiğin örneğini kolayca oluşturmasına olanak tanır. Ayrıca, bu güncellemelerin her biri, grafiğin mevcut durumuna tam erişimle işlenir ve geliştiricinin gereksiz hesaplamaları ortadan kaldırmasına ve daha sorunsuz kullanıcı ve müşteri deneyimleri oluşturmak için D3.js'nin gücünden yararlanmasına olanak tanır.