const getDataValue = data => { if (data != null && typeof data == "object") { return data.data; } else { return data; } }; const getTranslation = transform => { // Create a dummy g for calculation purposes only. This will never // be appended to the DOM and will be discarded once this function // returns. var g = document.createElementNS("http://www.w3.org/2000/svg", "g"); // Set the transform attribute to the provided string value. g.setAttributeNS(null, "transform", transform); // consolidate the SVGTransformList containing all transformations // to a single SVGTransform of type SVG_TRANSFORM_MATRIX and get // its SVGMatrix. var matrix = g.transform.baseVal.consolidate().matrix; // As per definition values e and f are the ones for the translation. return [matrix.e, matrix.f]; }; function inverse() { var rangeDiffOriginal = xScale.range()[1] - xScale.range()[0]; var index = 0; if (rangeDiffOriginal >= 0) { index = Math.floor( (arguments[0] - xScale.paddingOuter() - xScale.range()[0]) / xScale.bandwidth() ); } else { index = Math.floor( (xScale.range()[0] - (arguments[0] + xScale.paddingOuter())) / xScale.bandwidth() ); } if (index < 0 || index >= xScale.domain().length) { return ""; } else { return xScale.domain()[index]; } } function onBrushEnd(data) { if (!event || !event.x) return; else { const x = d3 .select("#brushContainer") .select(".selection") .node() .getAttribute("x"); let currentXPosition = parseFloat(x); const indexOfEndValue = rangeValues.indexOf(inverse(currentXPosition)); const nextIndexOfPoint = indexOfEndValue == 0 ? indexOfEndValue : Math.floor(indexOfEndValue / stepSize) == 0 ? 0 : Math.floor(indexOfEndValue / stepSize) * stepSize; const x1 = xScale(getDataValue(rangeValues[nextIndexOfPoint])); let x2 = xScale(getDataValue(rangeValues[nextIndexOfPoint + sliderSize])); if (nextIndexOfPoint + sliderSize > rangeValues.length - 1) { x2 = xScale( getDataValue( rangeValues[ nextIndexOfPoint + sliderSize > rangeValues.length - 1 ? rangeValues.length - 1 : nextIndexOfPoint + sliderSize ] ) ); x2 = x2 + xScale.bandwidth(); } d3.select(this) .transition() .call(brush.move, [x1, x2]); const x1Value = inverse(x1); const x2Value = inverse(x2 - 1); d3.select("#labelRef") .select("text") .text(x1Value == x2Value ? x1Value : x1Value + "-" + x2Value); console.log("start Point:", x1Value + "\tEnd Point:", x2Value); } } function onBrushMove(data) { data = data == null ? {} : data; const x = d3 .select("#brushContainer") .select(".selection") .node() .getAttribute("x"); data.x = x; d3.select("#arrowContainer").attr("transform", `translate(${data.x},0)`); d3.select("#labelRef").attr( "transform", `translate(${parseFloat(data.x) + (arrowCoOrdinates.x2 - arrowCoOrdinates.x1) / 2},0)` ); const currentXPosition = parseFloat(data.x); const indexOfEndValue = rangeValues.indexOf(inverse(currentXPosition)); const nextIndexOfPoint = indexOfEndValue == 0 ? indexOfEndValue : Math.floor(indexOfEndValue / stepSize) == 0 ? 0 : Math.floor(indexOfEndValue / stepSize) * stepSize; const x1 = xScale(getDataValue(rangeValues[nextIndexOfPoint])); let x2 = xScale(getDataValue(rangeValues[nextIndexOfPoint + sliderSize])); if (nextIndexOfPoint + sliderSize > rangeValues.length - 1) { x2 = xScale( getDataValue( rangeValues[ nextIndexOfPoint + sliderSize > rangeValues.length - 1 ? rangeValues.length - 1 : nextIndexOfPoint + sliderSize ] ) ); x2 = x2 + xScale.bandwidth(); } const x1Value = inverse(x1); const x2Value = inverse(x2 - 1); d3.select("#labelRef") .select("text") .text(x1Value == x2Value ? x1Value : x1Value + "-" + x2Value); } const rangeValues = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], width = 800, height = 100, arrowFillColor = "orange", arrowHeight = 10, arrowWidth = 9, margin = { left: 50, right: 50, top: 0, bottom: 0 }, arrowLabelFormatter = null; const xScale = d3 .scaleBand() .domain(rangeValues) .range([0, width - (margin.left + margin.right)]); d3.select("#svg") .attr("width", width) .attr("height", height); d3.select(".axisContainer").attr( "transform", `translate(${margin.left},${height / 2})` ); let index = parseInt(d3.select("#initialIndex").node().value), stepSize = parseInt(d3.select("#stepSize").node().value), sliderSize = parseInt(d3.select("#sliderSize").node().value); let arrowCoOrdinates = { x1: xScale(rangeValues[0]), y1: 0, x2: xScale(getDataValue(rangeValues[0 + sliderSize])), y2: 0 }; const brush = d3 .brushX() .extent([ [0, -(arrowHeight / 2)], [width - (margin.left + margin.right), arrowHeight] ]) .on("end", onBrushEnd) .on("brush", onBrushMove); function drawRangeSlider() { (index = parseInt(d3.select("#initialIndex").node().value)), (stepSize = parseInt(d3.select("#stepSize").node().value)), (sliderSize = parseInt(d3.select("#sliderSize").node().value)); arrowCoOrdinates = { x1: xScale(rangeValues[0]), y1: 0, x2: xScale(getDataValue(rangeValues[0 + sliderSize])), y2: 0 }; //CBT:Ticks STart d3.select("#axesContainer").html(""); let fontHeight; rangeValues.forEach((data, index) => { let values = data; if (data != null && typeof data == "object") { if (data.displayName) { values = data.displayName; } else if ( data.formatter != null && typeof data.formatter == "function" ) { values = data.formatter(data.data); } else { values = data.data; } } const ticksG = d3 .select("#axesContainer") .append("g") .attr("class", "ticks") .attr("transform", `translate(${xScale(getDataValue(data))},0)`); let wrappedTexts = []; if (values.toString().split("%^^%").length > 1) { const textData = values.split("%^^%"); const textG = ticksG.append("text").style("text-anchor", "middle"); wrappedTexts = textData.map(textD => { const tspan = textG .append("tspan") .classed("ticks-text", true) .style("font-size", "var(--small-text-size)") .attr("x", xScale.bandwidth() / 2) .style("fill", "#808080") .text(textD); return tspan; }); fontHeight = wrappedTexts[0].node().getBBox().height; } else { const text = ticksG .append("text") .classed("ticks-text", true) .style("text-anchor", "middle") .style("font-size", "var(--small-text-size)") .attr("x", xScale.bandwidth() / 2) .style("fill", "#808080") .text(values); fontHeight = d3 .select("#axesContainer") .select(".ticks text.ticks-text") .node() .getBBox().height; } if (wrappedTexts.length > 0) { let yPosition = 0; wrappedTexts.forEach(textPoint => { textPoint.attr("y", yPosition); yPosition = yPosition + fontHeight; }); } //CBT:tick line start ticksG .append("line") .attr("x1", 0) .attr("y1", -fontHeight) .attr("x2", 0) .attr("y2", -fontHeight + 4) .style("stroke", "#C4C4C4"); if (index == rangeValues.length - 1) { ticksG .append("line") .attr("x1", xScale.bandwidth() - 1) .attr("y1", -fontHeight) .attr("x2", xScale.bandwidth() - 1) .attr("y2", -fontHeight + 4) .style("stroke", "#C4C4C4"); } //CBT:tick line end }); //CBT:put axis down as font size d3.select("#axesContainer").attr("transform", `translate(0,${fontHeight})`); //CBT:add ticks line d3.select("#axesContainer") .append("line") .attr("x1", xScale(getDataValue(rangeValues[0]))) .attr("y1", -fontHeight) .attr( "x2", xScale(getDataValue(rangeValues[rangeValues.length - 1])) + xScale.bandwidth() ) .attr("y2", -fontHeight) .style("stroke", "#C4C4C4"); //CBT:Hide Overlapping Ticks text var selectedTextWidth = ""; let previousElement = null; d3.select("#axesContainer") .selectAll(".ticks") .each(function(text, i) { var that = this; let textWidth = d3 .select(that) .select(".ticks-text") .node() .getBBox().width; d3.select(that) .select(".ticks-text") .style("display", "block"); var position = getTranslation(that.getAttribute("transform")); if ( i == 0 || d3 .select(that) .select("text tspan") .empty() == false ) { selectedTextWidth = position[0] + textWidth; if ( previousElement != null && d3 .select(previousElement) .select("text tspan") .empty() == true ) { //CBT:hide previous ticks text if it doesn't contain tspan and it's text overlapped current text const positionPre = Utils.getTranslation( previousElement.getAttribute("transform") ); const textWidthPre = d3 .select(previousElement) .select(".ticks-text") .node() .getBBox().width; if (positionPre[0] + textWidthPre + 5 > position[0]) { d3.select(previousElement) .select(".ticks-text") .style("display", "none"); } } previousElement = that; } else { var currentTextStart = position[0]; if ( currentTextStart <= selectedTextWidth + 10 || currentTextStart > width ) { d3.select(that) .select(".ticks-text") .style("display", "none"); } else { selectedTextWidth = position[0] + textWidth; previousElement = that; } } }); //CBT:Ticks End //CBT:arrow start d3.select("#arrowRef").html(""); const x11 = arrowCoOrdinates.x1 + arrowWidth; const y11 = arrowCoOrdinates.y1 - arrowHeight / 2; const x12 = x11; const y12 = y11 + arrowHeight; const x21 = arrowCoOrdinates.x2 - arrowWidth; const y21 = arrowCoOrdinates.y2 - arrowHeight / 2; const x22 = x21; const y22 = y21 + arrowHeight; const lineX1 = arrowCoOrdinates.x1 + arrowWidth; const lineX2 = arrowCoOrdinates.x2 - arrowWidth; d3.select("#arrowRef") .attr("class", "range-arrow-slider ") .attr("x", arrowCoOrdinates.x1) .append("path") .attr( "d", `M${arrowCoOrdinates.x1} ${arrowCoOrdinates.y1} L${x11} ${y11} L${x12} ${y12} L${arrowCoOrdinates.x1} ${arrowCoOrdinates.y1} Z` ) .style("fill", arrowFillColor); d3.select("#arrowRef") .append("path") .attr( "d", `M${arrowCoOrdinates.x2} ${arrowCoOrdinates.y2} L${x21} ${y21} L${x22} ${y22} L${arrowCoOrdinates.x2} ${arrowCoOrdinates.y2} Z` ) .style("fill", arrowFillColor); d3.select("#arrowRef") .append("line") .attr("x1", lineX1) .attr("y1", arrowCoOrdinates.y1) .attr("x2", lineX2) .attr("y2", arrowCoOrdinates.y1) .style("stroke-width", 1) .style("stroke", arrowFillColor); const arrowRect = d3 .select("#arrowRef") .append("rect") .attr("x", arrowCoOrdinates.x1) .attr("y", y11) .attr("width", arrowCoOrdinates.x2 - arrowCoOrdinates.x1) .attr("height", arrowHeight) .style("fill", "transparent"); //CBT:Arrow End //CBT:Brush STart const scaleBandWidth = xScale.bandwidth() / 2; d3.select("#brushContainer").html(""); const brushG = d3.select("#brushContainer"); const brushSelection = brushG .insert("g") .attr("class", "brush") .attr("id", d => { return `brush-1`; }); brush(brushSelection); const x1 = xScale(getDataValue(rangeValues[index])); let x2 = xScale(getDataValue(rangeValues[index + sliderSize])); //CBT:this is for default index is greater than data value's length if (index + sliderSize > rangeValues.length - 1) { x2 = width - (margin.left + margin.right); } brush.move(brushSelection, [x1, x2]); d3.select("#brushContainer") .select(".overlay") .attr("pointer-events", "none"); d3.select("#brushContainer") .select(".selection") .style("fill", "transparent") .style("stroke", "transparent"); d3.select("#brushContainer") .selectAll(".handle") .style("pointer-events", "none"); //CBT:Brush ENd } drawRangeSlider(); function applyChanges() { drawRangeSlider(); }