implement base functionality
This commit is contained in:
parent
1c85a515b4
commit
4c7e9c67f4
@ -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?"
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"showAllDataPoints": {
|
"rowGap": {
|
||||||
"displayName": "Show all",
|
|
||||||
"type": {
|
"type": {
|
||||||
"bool": true
|
"numeric": true
|
||||||
}
|
},
|
||||||
|
"placeHolderText": "{value}%",
|
||||||
|
"displayName": "rowGap",
|
||||||
|
"description": "The distance moved during each cycle.(%)"
|
||||||
},
|
},
|
||||||
"fill": {
|
"columnGap": {
|
||||||
"displayName": "Fill",
|
|
||||||
"type": {
|
"type": {
|
||||||
"fill": {
|
"numeric": true
|
||||||
"solid": {
|
},
|
||||||
"color": true
|
"placeHolderText": "msec",
|
||||||
}
|
"displayName": "columnGap",
|
||||||
}
|
"description": "How long a cycle takes? (msec)"
|
||||||
}
|
|
||||||
},
|
|
||||||
"fillRule": {
|
|
||||||
"displayName": "Color saturation",
|
|
||||||
"type": {
|
|
||||||
"fill": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"fontSize": {
|
|
||||||
"displayName": "Text Size",
|
|
||||||
"type": {
|
|
||||||
"formatting": {
|
|
||||||
"fontSize": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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)"
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dataViewMappings": [
|
"dataViewMappings": [{
|
||||||
{
|
"categorical": {
|
||||||
"categorical": {
|
"categories": {
|
||||||
"categories": {
|
"for": {
|
||||||
"for": {
|
"in": "urls"
|
||||||
"in": "category"
|
|
||||||
},
|
|
||||||
"dataReductionAlgorithm": {
|
|
||||||
"top": {}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"values": {
|
"dataReductionAlgorithm": {
|
||||||
"select": [
|
"top": {}
|
||||||
{
|
|
||||||
"bind": {
|
|
||||||
"to": "measure"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}]
|
||||||
}
|
}
|
26
pbiviz.json
26
pbiviz.json
@ -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
31
src/css/picsScroller.css
Normal 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%;
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
||||||
|
137
src/visual.ts
137
src/visual.ts
@ -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;
|
private initial() {
|
||||||
if (document) {
|
if (this.innerContainer) {
|
||||||
const new_p: HTMLElement = document.createElement("p");
|
this.innerContainer.remove();
|
||||||
new_p.appendChild(document.createTextNode("Update count:"));
|
};
|
||||||
const new_em: HTMLElement = document.createElement("em");
|
this.innerContainer = document.createElement("div");
|
||||||
this.textNode = document.createTextNode(this.updateCount.toString());
|
this.innerContainer.className = "pics-sroller-inner-container";
|
||||||
new_em.appendChild(this.textNode);
|
this.container.appendChild(this.innerContainer);
|
||||||
new_p.appendChild(new_em);
|
|
||||||
this.target.appendChild(new_p);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
Loading…
Reference in New Issue
Block a user