Creating sites with modular Typescript, JQuery, requireJS and D3js (v4)

Standard Approach for a page is to setup div with an ID that will become the SVG Canvas

  • The SVG Canvas object (implements ISvgCanvas)
    • maitains identity with the page via div:id, fetching attribute data for canvas dimensions
    • Creates a Defines child object with stores filters, markers that can be used by any of the child objects
    • Creates a d3v4 selection object that other controls will use for appended graphics
    • The SVG Canvas object is passed to all child controls so they can
      • Figure out the extents of the window
      • Access the Defs (or create additional ones at the correct level)
      • Append to the main SVG node

Note – the modular (AMD/EC5) approach uses the following imports from a d3 modular typings:

import * as d3_selection from “d3-selection”;
import * as $ from “jquery”;

export type d3v4Selection = d3_selection.Selection<d3_selection.BaseType, any, any, any>;

ISvgCanvas

export interface ISvgCanvas{
 id:String;
 defines:ICanvasDefines; 
 svgCanvas:d3v4Selection;
 width:number;
 height:number;
}

ICanvasDefines – for addding gradients, filters, markers to the <defs> child of the canvas

export interface ICanvasDefines{
 AddLinearGradient(gradientId: string): d3v4Selection;
 AddGaussianBlurFilter(filterID: string): d3v4Selection;
 //stop1:string='70%',stop1color:string='lightgrey', stop2:string= '100%', stop2color:string= 'gray'
 AddRadialGradiantWithTwoStops(gradientID:string, stop1?:string,stop1color?:string, stop2?:string, stop2color?:string): d3v4Selection;
 AddArrow(markerID:string,markerWidth?:number,markerHeight?:number,refX?:number,refY?:number): d3v4Selection
}

Canvas Object (SvgD3Canvas implements ISvgCanvas)

export class SvgD3Canvas implements ISvgCanvas{

 private _container:JQuery;
 private _d3SVG:d3v4Selection;
 private _width:number;
 private _height:number;
 private _defines:ICanvasDefines;


 constructor(divID:string){

 this._container = $(divID);
 let itemHeight = this._container.attr("item-height");
 if(!itemHeight){
   itemHeight = this._container.css("height").replace(/[^-\d\.]/g, '');;
 }
 if(!itemHeight){
   itemHeight = "2000";
 }

 let itemwidth = this._container.attr("item-width");
 if(!itemwidth){
   itemwidth = this._container.css("width").replace(/[^-\d\.]/g, '');
 }
 if(!itemwidth){
   itemwidth = "2000";
 }


 this._height = Math.round(Number(itemHeight));
 this._width =Math.round(Number(itemwidth));
 this._d3SVG = d3_selection.select(divID).append("svg").attr("width",this._width).attr("height", this._height);
 this._defines= new SVGdefsHelper(this._d3SVG);
 
 }

 public get width():number{
 return this._width;
 }
 public get height():number{
 return this._height;
 }
 public get svgCanvas():d3v4Selection{
 return this._d3SVG;
 }

 public get defines():ICanvasDefines{
 return this._defines;
 }
}

Defines Object


export class SVGdefsHelper implements ICanvasDefines {

 _svgDefsSection: d3v4Selection;

 constructor(svgDefs: d3v4Selection) {
 this._svgDefsSection = svgDefs;
 }


 public AddRadialGradiantWithTwoStops(gradientID:string, stop1:string='70%',stop1color:string='lightgrey', stop2:string= '100%', stop2color:string= 'gray'): d3v4Selection{
 let gradient = this._svgDefsSection.append('radialGradient')
 .attr("id", gradientID);

 gradient.append("stop").attr("offset", stop1).attr("stop-color", stop1color);
 gradient.append("stop").attr("offset", stop2).attr("stop-color", stop2color);

 return gradient;
 }

 public AddArrow(markerID:string,markerWidth:number=10,markerHeight:number=10,refX:number=6,refY:number=3): d3v4Selection{
 let marker = this._svgDefsSection.append('marker').attr("class","def-marker-arrow").attr("id", markerID)
 .attr("markerWidth", markerWidth)
 .attr("markerHeight",markerHeight)
 .attr("refX",refX)
 .attr("refY",refY)
 .attr("orient","auto")
 .attr("markerUnits", "strokeWidth");

 let path = marker.append("path").attr("d","M0,0 L0,6 L9,3 z").attr("class","def-marker-arrow");
 
 return marker;

 
// <marker id="arrow" markerWidth="10" markerHeight="10" refX="0" refY="3" orient="auto" markerUnits="strokeWidth">
// <path d="M0,0 L0,6 L9,3 z" fill="#f00" />
// </marker>
 }
 public AddRadialGradient(gradiantParams: IGradientStop2): d3v4Selection {
 // <radialGradient id="exampleGradient">
 // <stop offset="70%" stop-color="lightgrey"/>
 // <stop offset="100%" stop-color="gray"/>
 //</radialGradient>
 var grayCircularRadialGrad = { id: 'grayRadial', stop1: '70%', stop1color: 'lightgrey', stop2: '100%', stop2color: 'gray' };
 var gradient = this._svgDefsSection.append('radialGradient')
 .attr("id", gradiantParams.id);

 gradient.append("stop").attr("offset", gradiantParams.stop1).attr("stop-color", gradiantParams.stop1color);
 gradient.append("stop").attr("offset", gradiantParams.stop2).attr("stop-color", gradiantParams.stop2color);

 return gradient;

 }

 public AddLinearGradient(gradientId: string): d3v4Selection {

 //<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
 // <stop offset="0%" style="stop-color:rgb(255,255,0);stop-opacity:1" />
 // <stop offset="100%" style="stop-color:rgb(255,0,0);stop-opacity:1" />
 // </linearGradient>

 var mainGradient = this._svgDefsSection.append('linearGradient')
 .attr("id", gradientId)
 .attr("x1", "0%")
 .attr("y1", "0%")
 .attr("x2", "100%")
 .attr("y2", "0%")

 mainGradient.append('stop')
 .attr('class', 'stop-left')
 .attr('offset', '0%')
 .attr('style', 'stop-color:rgb(255,255,0);stop-opacity:1');

 mainGradient.append('stop')
 .attr('class', 'stop-left')
 .attr('offset', '100%')
 .attr('style', 'stop-color:rgb(255,0,0);stop-opacity:1');

 return mainGradient;


 }

 public AddGaussianBlurFilter(filterID: string): d3v4Selection {
 //<filter id="f3" x="0" y="0" width="200%" height="200%">
 // <feOffset result="offOut" in="SourceAlpha" dx="20" dy="20" />
 // <feGaussianBlur result="blurOut" in="offOut" stdDeviation="10" />
 // <feBlend in="SourceGraphic" in2="blurOut" mode="normal" />
 // </filter>
 var mainFilter = this._svgDefsSection.append('filter')
 .attr("id", filterID)
 .attr("x", "0")
 .attr("y", "0")
 .attr("width", "200%")
 .attr("height", "200%");

 mainFilter.append("feOffset")
 .attr("result", "offOut")
 .attr("in", "SourceAlpha")
 .attr("dx", "20")
 .attr("dy", "20");

 mainFilter.append("feGaussianBlur")
 .attr("result", "blurOut")
 .attr("in", "offOut")
 .attr("stdDeviation", "10");

 mainFilter.append("feBlend")
 .attr("in", "SourceGraphic")
 .attr("in2", "blurOut")
 .attr("mode", "normal");

 return mainFilter;


 }
 public AddFilterToDefs(filterID: string): d3v4Selection {
 //<filter id="f3" x="0" y="0" width="200%" height="200%">
 // <feOffset result="offOut" in="SourceAlpha" dx="20" dy="20" />
 // <feGaussianBlur result="blurOut" in="offOut" stdDeviation="10" />
 // <feBlend in="SourceGraphic" in2="blurOut" mode="normal" />
 // </filter>
 var mainFilter = this._svgDefsSection.append('filter')
 .attr("id", filterID)
 .attr("x", "0")
 .attr("y", "0")
 .attr("width", "200%")
 .attr("height", "200%");

 mainFilter.append("feOffset")
 .attr("result", "offOut")
 .attr("in", "SourceAlpha")
 .attr("dx", "20")
 .attr("dy", "20");

 mainFilter.append("feGaussianBlur")
 .attr("result", "blurOut")
 .attr("in", "offOut")
 .attr("stdDeviation", "10");

 mainFilter.append("feBlend")
 .attr("in", "SourceGraphic")
 .attr("in2", "blurOut")
 .attr("mode", "normal");

 return mainFilter;


 }
 public AddWhiteToColorGradient(gradientID: string, color: string): d3v4Selection {
 //<linearGradient id="gradientOrange" x1="0%" y1="0%" x2="0%" y2="100%" spreadMethod="pad">
 // <stop offset="000%" stop-color="#FF9600" stop-opacity="1"/>
 // <stop offset="050%" stop-color="#FFFFFF" stop-opacity="1"/>
 // <stop offset="100%" stop-color="#000000" stop-opacity="1"/>
 //</linearGradient>

 //<linearGradient id="gradientGreen" x1="0%" y1="0%" x2="0%" y2="100%" spreadMethod="pad">
 // <stop offset="000%" stop-color="#22DD00" stop-opacity="1"/>
 // <stop offset="050%" stop-color="#FFFFFF" stop-opacity="1"/>
 // <stop offset="100%" stop-color="#000000" stop-opacity="1"/>
 //</linearGradient>

 var gradient = this._svgDefsSection.append('linearGradient')
 .attr("id", gradientID)
 .attr("x1", "0%")
 .attr("y1", "0%")
 .attr("x2", "0%")
 .attr("y2", "100%")

 gradient.append('stop')
 .attr('offset', '0%')
 .attr('stop-color', color)
 .attr('stop-opacity', 1);


 gradient.append('stop')
 .attr('offset', '50%')
 .attr('stop-color', "#FFFFFF")
 .attr('stop-opacity', 1);

 gradient.append('stop')
 .attr('offset', '100%')
 .attr('stop-color', "#000000")
 .attr('stop-opacity', 1);

 return gradient;

 }
}

D3 Sliders – SVG

SVG Slider Libraries that are close to desired behavior

Web Page

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>Title</title>
 <script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
 <script src="simpleD3Slider.js"></script>
</head>

<body>
<div id="vis"></div>
</body>

<script>

 var svg = d3.select("#vis").append("svg").attr("width", 1000).attr("height", 700),
 slider1 = new simpleSlider(),
 slider2 = new simpleSlider(),
 circle = svg.append("circle").attr("cx", 50).attr("cy", 100).attr("r", 30);


 slider1.width(200).x(30).y(200).value(1.0).event(function(){
 circle.attr("r", 30 * slider1.value());
 });

 slider2.width(200).x(30).y(230).value(0.5).event(function(){
 circle.attr("cx", 50 + (170 * slider2.value()));
 });

 svg.call(slider1);
 svg.call(slider2);

</script>
</html>
/* Simple, reusable slider in pure d3 */

function simpleSlider () {

 var width = 100,
 value = 0.5, /* Domain assumes to be [0 - 1] */
 event,
 x = 0,
 y = 0;

 function slider (selection) {

 //Line to represent the current value
 var valueLine = selection.append("line")
 .attr("x1", x)
 .attr("x2", x + (width * value))
 .attr("y1", y)
 .attr("y2", y)
 .style({stroke: "#51CB3F",
 "stroke-linecap": "round",
 "stroke-width": 6 });

 //Line to show the remaining value
 var emptyLine = selection.append("line")
 .attr("x1", x + (width * value))
 .attr("x2", x + width)
 .attr("y1", y)
 .attr("y2", y)
 .style({
 "stroke": "#ECECEC",
 "stroke-linecap": "round",
 "stroke-width": 6
 });

 var drag = d3.behavior.drag().on("drag", function() {
 var newX = d3.mouse(this)[0];

 if (newX < x)
 newX = x;
 else if (newX > x + width)
 newX = x + width;

 value = (newX - x) / width;
 valueCircle.attr("cx", newX);
 valueLine.attr("x2", x + (width * value));
 emptyLine.attr("x1", x + (width * value));

 if (event)
 event();

 d3.event.sourceEvent.stopPropagation();
 })

 //Draggable circle to represent the current value
 var valueCircle = selection.append("circle")
 .attr("cx", x + (width * value))
 .attr("cy", y)
 .attr("r", 8)
 .style({
 "stroke": "black",
 "stroke-width": 1.0,
 "fill": "white"
 })
 .call(drag);
 }


 slider.x = function (val) {
 x = val;
 return slider;
 }

 slider.y = function (val) {
 y = val;
 return slider;
 }

 slider.value = function (val) {
 if (val) {
 value = val;
 return slider;
 } else {
 return value;
 }
 }

 slider.width = function (val) {
 width = val;
 return slider;
 }

 slider.event = function (val) {
 event = val;
 return slider;
 }

 return slider;
}