implement base functionality

This commit is contained in:
沐见南 2020-09-05 23:25:46 +08:00
parent 1c85a515b4
commit 4c7e9c67f4
5 changed files with 242 additions and 104 deletions

View File

@ -1,84 +1,73 @@
{ {
"dataRoles": [ "dataRoles": [{
{ "displayName": "urls",
"displayName": "Category Data", "name": "urls",
"name": "category", "kind": "Grouping",
"kind": "Grouping" "description": "Urls of pictures, example: 'http://www.example.com/xx.png'"
}, }],
{
"displayName": "Measure Data",
"name": "measure",
"kind": "Measure"
}
],
"objects": { "objects": {
"dataPoint": { "layout": {
"displayName": "Data colors", "displayName": "layout",
"properties": { "properties": {
"defaultColor": { "rowsCount": {
"displayName": "Default color",
"type": { "type": {
"fill": { "numeric": true
"solid": { },
"color": true "displayName": "rowsCount",
"description": "How many rows?"
},
"rowGap": {
"type": {
"numeric": true
},
"placeHolderText": "{value}%",
"displayName": "rowGap",
"description": "The distance moved during each cycle.(%)"
},
"columnGap": {
"type": {
"numeric": true
},
"placeHolderText": "msec",
"displayName": "columnGap",
"description": "How long a cycle takes? (msec)"
} }
}
},
"animation": {
"displayName": "animation",
"properties": {
"feet": {
"type": {
"numeric": true
},
"placeHolderText": "{value}%",
"displayName": "feet",
"description": "The distance moved during each cycle.(%)"
},
"interval": {
"type": {
"numeric": true
},
"placeHolderText": "numeric",
"displayName": "interval",
"description": "How long a cycle takes? (msec)"
}
} }
} }
}, },
"showAllDataPoints": { "dataViewMappings": [{
"displayName": "Show all",
"type": {
"bool": true
}
},
"fill": {
"displayName": "Fill",
"type": {
"fill": {
"solid": {
"color": true
}
}
}
},
"fillRule": {
"displayName": "Color saturation",
"type": {
"fill": {}
}
},
"fontSize": {
"displayName": "Text Size",
"type": {
"formatting": {
"fontSize": true
}
}
}
}
}
},
"dataViewMappings": [
{
"categorical": { "categorical": {
"categories": { "categories": {
"for": { "for": {
"in": "category" "in": "urls"
}, },
"dataReductionAlgorithm": { "dataReductionAlgorithm": {
"top": {} "top": {}
} }
},
"values": {
"select": [
{
"bind": {
"to": "measure"
} }
} }
] }]
}
}
}
]
} }

View File

@ -1 +1,25 @@
{"visual":{"name":"picsScroller","displayName":"picsScroller","guid":"picsScroller4F6E5B21FAB94F2081F33542CFAD9E09","visualClassName":"Visual","version":"1.0.0","description":"","supportUrl":"","gitHubUrl":""},"apiVersion":"2.6.0","author":{"name":"","email":""},"assets":{"icon":"assets/icon.png"},"externalJS":null,"style":"style/visual.less","capabilities":"capabilities.json","dependencies":null,"stringResources":[]} {
"visual": {
"name": "picsScroller",
"displayName": "picsScroller",
"guid": "picsScroller4F6E5B21FAB94F2081F33542CFAD9E09",
"visualClassName": "Visual",
"version": "1.0.0",
"description": "scroll pictures",
"supportUrl": "https://blog.mujiannan.me",
"gitHubUrl": "https://www.github.com/mujiannan"
},
"apiVersion": "2.6.0",
"author": {
"name": "shennan",
"email": "littlesand@Outlook.com"
},
"assets": {
"icon": "assets/icon.png"
},
"externalJS": [],
"style": "style/visual.less",
"capabilities": "capabilities.json",
"dependencies": null,
"stringResources": []
}

31
src/css/picsScroller.css Normal file
View File

@ -0,0 +1,31 @@
div.pics-sroller-inner-container {
position: absolute;
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
}
div.pics-scroller-row-container {
width: 100%;
overflow-x: hidden;
overflow-y: nowrap;
white-space: nowrap;
display: flex;
flex-direction: row;
}
div.pics-scroller-img-container {
height: 100%;
width: auto;
display: inline-block;
}
div.pics-scroll-invisible-img-container {
display: none;
}
img.pics-scroller-img {
height: 100%;
}

View File

@ -30,19 +30,16 @@ import { dataViewObjectsParser } from "powerbi-visuals-utils-dataviewutils";
import DataViewObjectsParser = dataViewObjectsParser.DataViewObjectsParser; import DataViewObjectsParser = dataViewObjectsParser.DataViewObjectsParser;
export class VisualSettings extends DataViewObjectsParser { export class VisualSettings extends DataViewObjectsParser {
public dataPoint: dataPointSettings = new dataPointSettings(); public animation: AnimationSettings = new AnimationSettings();
} public layout:LayoutSettings=new LayoutSettings();
}
export class dataPointSettings {
// Default color
public defaultColor: string = "";
// Show all
public showAllDataPoints: boolean = true;
// Fill
public fill: string = "";
// Color saturation
public fillRule: string = "";
// Text Size
public fontSize: number = 12;
}
export class AnimationSettings {
public feet:number=1;//n% * width
public interval:number=20;//ms
}
export class LayoutSettings{
public rowsCount:number=1;
public rowGap:number=2;//n% * height
public columnGap:number=2;//n% * width
}

View File

@ -27,6 +27,7 @@
import "core-js/stable"; import "core-js/stable";
import "./../style/visual.less"; import "./../style/visual.less";
import "./css/picsScroller.css";
import powerbi from "powerbi-visuals-api"; import powerbi from "powerbi-visuals-api";
import VisualConstructorOptions = powerbi.extensibility.visual.VisualConstructorOptions; import VisualConstructorOptions = powerbi.extensibility.visual.VisualConstructorOptions;
import VisualUpdateOptions = powerbi.extensibility.visual.VisualUpdateOptions; import VisualUpdateOptions = powerbi.extensibility.visual.VisualUpdateOptions;
@ -37,34 +38,130 @@ import DataView = powerbi.DataView;
import VisualObjectInstanceEnumerationObject = powerbi.VisualObjectInstanceEnumerationObject; import VisualObjectInstanceEnumerationObject = powerbi.VisualObjectInstanceEnumerationObject;
import { VisualSettings } from "./settings"; import { VisualSettings } from "./settings";
import { timeout } from "d3";
export interface IPicsScrollerData {
urls: string[];
}
export class Visual implements IVisual { export class Visual implements IVisual {
private target: HTMLElement; private container: HTMLElement;
private updateCount: number;
private settings: VisualSettings; private settings: VisualSettings;
private textNode: Text; private innerContainer!: HTMLDivElement;
private animationPlaying: boolean = true;//控制动画暂停
constructor(options: VisualConstructorOptions) { constructor(options: VisualConstructorOptions) {
console.log('Visual constructor', options); this.container = options.element;
this.target = options.element;
this.updateCount = 0;
if (document) {
const new_p: HTMLElement = document.createElement("p");
new_p.appendChild(document.createTextNode("Update count:"));
const new_em: HTMLElement = document.createElement("em");
this.textNode = document.createTextNode(this.updateCount.toString());
new_em.appendChild(this.textNode);
new_p.appendChild(new_em);
this.target.appendChild(new_p);
} }
private initial() {
if (this.innerContainer) {
this.innerContainer.remove();
};
this.innerContainer = document.createElement("div");
this.innerContainer.className = "pics-sroller-inner-container";
this.container.appendChild(this.innerContainer);
} }
public update(options: VisualUpdateOptions) { public update(options: VisualUpdateOptions) {
this.settings = Visual.parseSettings(options && options.dataViews && options.dataViews[0]); this.settings = Visual.parseSettings(options && options.dataViews && options.dataViews[0]);
console.log('Visual update', options); this.initial();
if (this.textNode) {
this.textNode.textContent = (this.updateCount++).toString(); let data: IPicsScrollerData = { urls: <string[]>options.dataViews[0].categorical.categories[0].values };
let feet = options.viewport.width / 100 * this.settings.animation.feet;
let interval = this.settings.animation.interval;
let rowGap = options.viewport.height / 100 * this.settings.layout.rowGap;
let columnGap = options.viewport.width / 100 * this.settings.layout.columnGap;
let rowsCount=this.settings.layout.rowsCount;
//处理输入
if (!(data.urls.length > 0) || !(rowsCount! > 0)) {
console.info("picsScroller error: (source_url.length,rowsCount) ", data.urls.length + "_" + rowsCount);
return;
};
if (rowsCount > data.urls.length) {
rowsCount = data.urls.length;
};
let brandsCount = data.urls.length;
let unitsCountPerRow = Math.floor(brandsCount / rowsCount);
let logoUrlsArr: string[][] = [];
//转换url列表形态
for (let i = 0; i < rowsCount; i++) {
logoUrlsArr.push(data.urls.slice(i * unitsCountPerRow, Math.min((i + 1) * unitsCountPerRow, data.urls.length)));
};
//分成rowsCount行
for (let rowNum = 0; rowNum < rowsCount; rowNum++) {
let rowContainer = document.createElement('div');
this.innerContainer.appendChild(rowContainer);
rowContainer.classList.add('pics-scroller-row-container');
if (rowNum > 0) {
rowContainer.style.marginTop = rowGap + "px";
};
rowContainer.setAttribute('id', 'pics-scroller-row-container' + rowNum);
rowContainer.setAttribute("style", "height:" + 100 / rowsCount + "%;");
rowContainer.setAttribute("data-animation-playing", 'false');
rowContainer.setAttribute("data-display-placehold", "false");
//双倍图片容器,第一组用来显示,第二组放上去暂时隐藏
//如果第一组发生了溢出,就显示出第二组并应用滚动动画
for (let i = 0; i < 2; i++) {
for (let j = 0; j < logoUrlsArr[rowNum].length; j++) {
let logoContainer = document.createElement('div');
logoContainer.className = "pics-scroller-img-container";
if (i == 1) {
logoContainer.classList.add("pics-scroll-invisible-img-container");
};
let logo = document.createElement('img');
logo.style.margin = "0px " + columnGap / 2 + "px";
logo.setAttribute("class", "pics-scroller-img");
logo.setAttribute("src", logoUrlsArr[rowNum][j]);
logoContainer.appendChild(logo);
rowContainer.appendChild(logoContainer);
};
}
//重新激活动画
if (this.animationPlaying) {
this.activateAnimation();
}
//假无限滚动
window.setInterval(this.scrollLeftInfinity, interval, rowContainer, feet);
};
}
public activateAnimation() {
this.animationPlaying = true;
let rowContainers = this.innerContainer.getElementsByClassName("pics-scroller-row-container");
for (let i = 0; i < rowContainers.length; i++) {
rowContainers[i].setAttribute("data-animation-playing", "true");
} }
} }
private suspendAnimation() {
let rowContainers = this.innerContainer.getElementsByClassName("pics-scroller-row-container");
for (let i = 0; i < rowContainers.length; i++) {
rowContainers[i].setAttribute("data-animation-playing", "false");
}
}
//定义无限向左滚动函数
private scrollLeftInfinity(obj: HTMLDivElement, feet: number) {
let playAnimation = <string>obj.getAttribute("data-animation-playing");
let displayPlaceHold = <string>obj.getAttribute("data-display-placehold");
if (playAnimation == "false") {
return;
} else if (playAnimation == "true" && displayPlaceHold == "false" && obj.scrollWidth > obj.clientWidth) {
let invisibleImgContainers = obj.getElementsByClassName("pics-scroll-invisible-img-container");
while (invisibleImgContainers.length > 0) {
invisibleImgContainers[0].classList.remove("pics-scroll-invisible-img-container");
}
obj.setAttribute("data-display-placehold", "true");
}
if (obj.scrollWidth > obj.clientWidth) {
if (obj.scrollLeft >= obj.scrollWidth / 2) {
obj.scrollLeft = 0;
} else {
obj.scrollLeft += feet;
};
}
}
private static parseSettings(dataView: DataView): VisualSettings { private static parseSettings(dataView: DataView): VisualSettings {
return <VisualSettings>VisualSettings.parse(dataView); return <VisualSettings>VisualSettings.parse(dataView);