Forum Discussion

MawashiKid's avatar
MawashiKid
Resolver II
9 years ago

Power BI Visuals -Dimple.js Trellis sample...

Hi,

I stumbled on an article on LinkedIn where the author claimed he managed
to run a dimple.js trellis sample on Power BI Visuals.
https://www.linkedin.com/pulse/power-bi-fast-prototyping-dimpled3-custom-visual-martin-x
I decided to give it a try as I thought this would be a very good initiative to test external
libraries. One thing to keep in mind with dimple.js is it depends mainly on d3.js.
[d3.v4.. in this case] to work. So d3.js must appear first in directives hierarchy
otherwise dimple.js won't be able to run.

d3.v4.js: http://d3js.org/d3.v4.min.js //1st
'---> dimplejs: http://dimplejs.org/dist/dimple.v2.3.0.min.js //2nd

So I came up with this code based on information. I wish the author had provided
a complete working .

The whole code shows no error on pbiviz package compilation,

though when run in Power BI Desktop or testing on Power BI Online, nothing shows up... :( 

I'm using a similar csv mockup data sample:

 

I aslo checked visual's underlying data view and it seemed ok, so I presume something is obviously missing as dimple.js may not be recognized...

So here's what I came up with. 

 

The application hierarchy


tsconfigj.son:

{
  "compilerOptions": {
    "allowJs": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "target": "ES5",
    "sourceMap": true,
    "out": "./.tmp/build/visual.js"
  },
  "files": [
    ".api/v1.5.0/PowerBI-visuals.d.ts",
     "typings/index.d.ts",
     "src/trellischart.d.ts",
     "src/trellischart.js",
     "src/visual.ts"
  ]
}

pbiviz.json:

 "externalJS": ["node_modules/d3v4/build/d3.js","extlib/dimple.v2.3.0.js"], <--------
  "style": "style/visual.less",
  "capabilities": "capabilities.json",
  "dependencies": "dependencies.json"

capabilities.json:

{
    "dataRoles": [
        {
            "displayName": "Date",
            "name": "Date",
            "kind": "Grouping"
        },{
            "displayName": "Month",
            "name": "Month",
            "kind": "Grouping"
        },
        {
            "displayName": "Category",
            "name": "Category",
            "kind": "Grouping"
        },
        {
            "displayName": "Value",
            "name": "Value",
            "kind": "Measure"
        }
    ],
    "dataViewMappings": [
        {
            "conditions": [
                    {
                        "Date": { "max": 1 }, "Month": { "max": 1 }, 
                        "Category": { "max": 1 }, "Value": { "max": 1 }
                    }
                ]
                ,"categorical": {
                    "categories": {
                        "for": { "in": "Date" },
                        "dataReductionAlgorithm": { "top": {} }
                    },
                    "values": {
                        "select": [
                            { "bind": { "to": "Month" } },
                            { "bind": { "to": "Category" } },
                            { "bind": { "to": "Value" } }
                        ]
                    } 
                }
        }
    ]
}

src/tresllischart.d.ts:

declare var dimple: any;
declare var trellichart: any;

src/trellischart.js:

/// <amd-dependency path='external/dimple.v2.3.0.js'>

function loadTrellis(element, dataView, config) {
	console.log(config);
    var trellis = element;

	trellis.style('width', config.viewport.width)
  		.style('height',config.viewport.height);
    
	// var x = convert(dataView);
	var data = convert(dataView);//loadData(); //load data from here
	//d3.tsv("/data/example_data.tsv", function (data) {

 	// Get a unique list of dates
    var months = dimple.getUniqueValues(data.rows, data.date.Date);

      // Set the bounds for the charts
      var row = 0,
          col = 0,
          top = 25,
          left = 60,
          inMarg = 15,
          width = 115,
          height = 90,
          totalWidth = config.viewport.width*.9;//;parseFloat(trellis.attr("width"));

      // Pick the latest 12 dates
      months = months.slice(months.length - 12);

      // Draw a chart for each of the 12 dates
      months.forEach(function (month) {
          
          // Wrap to the row above
          if (left + ((col + 1) * (width + inMarg)) > totalWidth) {
            row += 1;
            col = 0;
          }
          
          // Filter for the month in the iteration
          var chartData = dimple.filterData(data.rows, data.date.Date, month);
          
          // Use d3 to draw a text label for the month
          trellis
            .append("text")
                .attr("x", left + (col * (width + inMarg)) + (width / 2))
                .attr("y", top + (row * (height + inMarg)) + (height / 2) + 12)
                .style("font-family", "sans-serif")
                .style("text-anchor", "middle")
                .style("font-size", "28px")
                .style("opacity", 0.2)
                .text(chartData[0].Month.substring(0, 3));
          
          // Create a chart at the correct point in the trellis
          var myChart = new dimple.chart(trellis, chartData);
          myChart.setBounds(
            left + (col * (width + inMarg)),
            top + (row * (height + inMarg)),
            width,
            height);
          
          // Add x and fix ordering so that all charts are the same
          var x = myChart.addCategoryAxis("x", data.axis.x);
          x.addOrderRule(data.axis.x);
          
          // Add y and fix scale so that all charts are the same
          var y = myChart.addMeasureAxis("y", data.axis.y);
          y.overrideMax = 1500;
          
          // Draw the bars.  Passing null here would draw all bars with
          // the same color.  Passing owner second colors by owner, which
          // is normally bad practice in a bar chart but works in a trellis.
          // Month is only passed here so that it shows in the tooltip.
          myChart.addSeries([data.date.Month, data.axis.x], dimple.plot.bar);

          // Draw the chart
          myChart.draw();

          // Once drawn we can access the shapes
          // If this is not in the first column remove the y text
          if (col > 0) {
            y.shapes.selectAll("text").remove();
          }
          // If this is not in the last row remove the x text
          if (row < 2) {
             x.shapes.selectAll("text").remove();
          }
          // Remove the axis labels
          y.titleShape.remove();
          x.titleShape.remove();

          // Move to the next column
          col += 1;

      }, this);
}


function convert(dataView)
{

	var DateCol = -1, MonthCol = -1, CategoryCol = -1, ValueCol = -1, ret = []; 

	if (dataView && dataView.categorical && dataView.metadata && dataView.metadata.columns) { //&& dataView.categorical.categories
		var metadataColumns = dataView.metadata.columns;
		//step 1: 1)find correct column index in meta data
		for (var i = 0; i < metadataColumns.length; i++) {
			var col = metadataColumns[i];
			if (col.roles) {
				if (col.roles['Date'])
					DateCol = i;
				else if (col.roles['Month'])
					MonthCol = i;
				else if (col.roles['Category'])
					CategoryCol = i;
				else if (col.roles['Value'])
					ValueCol = i;
			}
		}
		function getMonth(d)
		{
			function pad(s) { return (s < 10) ? '0' + s : s; }
			return [pad("1"), pad(d.getMonth() + 1), d.getFullYear()].join('/')
		}
		if (dataView && dataView.table) {
			//step 2: convert table row by row into correct columns of graph
			dataView.table.rows.forEach(function (item) {
				ret.push({"Date":getMonth(item[DateCol]),"Month":item[MonthCol], "Category":item[CategoryCol], "Value":item[ValueCol]});
			});
        }
		
	}//if
	return {axis:{ x: "Category", y: "Value"}
			, date:{ Date:"Date", Month:"Month"}
			, rows: ret
		};
}

src/visual.ts

// <amd-dependency path='external/dimple.v2.3.0.js'  name='dimple'>
/// <amd-dependency path='trellichart'>

module powerbi.extensibility.visual {
    /// <reference types="d3" />
    export class Visual implements IVisual {
        
        private svg: d3.Selection<SVGElement>;
        private ele: any;

        constructor(options: VisualConstructorOptions) {
            let ele = options.element;
            let svg = this.svg = d3.select(options.element)
                .append('svg').classed('trellis', true);
        }

        public update(options: VisualUpdateOptions) {
            let dataView = options.dataViews[0];
            debugger;            
            loadTrellis(this.svg, dataView, options);
        }
    }
}


So if anyone has a solution to make this work, then be my guest...
Any help would be appreciated. Thanks.

 



1 Reply

  • v-chuncz-msft's avatar
    v-chuncz-msft
    Community Support

    MawashiKid,

     

    I've done a quick test. You may add try...catch statement for troubleshooting. I'm sure you will be able to get it solved.